功能背景
大部网站都有菜单功能, 每个页面都有ajax更新页数据请求, 如何将页面上的所有菜单的页面都保存下来本地PC? 后续可以在本地PC上浏览页面。
前面一个文章 利用phantomjs可以抓取单个页面的并保存到PC, 可以本地浏览。
http://www.cnblogs.com/lightsong/p/5971701.html
每个页面ajax数据更新, 需要等待若干时间, 所以在将页面保存PC的时刻, 需要在ajax数据返回之后,
故需要在phantomjs代码中需要控制等待足够时间, 以确保页面的ajax更新执行完毕。 一般页面这个等待时间并不算长, 1-2完成。
但是对于树状的菜单结构, 如何保证一个菜单都被访问过, 页面都被保存到PC, 然后再访问第二个菜单, 执行第二个菜单的访问和保存工作?
如果每个菜单访问没有异步问题, 直接使用菜单的树的遍历机制即可。 但是树的遍历,每个节点访问都要考虑异步性访问, 正常的遍历就无能为力了。
事实上, 这种异步业务执行的 流程控制, 是工作流研究的范畴。
工作流
http://blog.csdn.net/wuluopiaoxue/article/details/6522908
工作流是将一组任务组织起来完成某个经营过程。在工作流中定义了任务的触发顺序和触发条件。每个任务可以由一个或多个软件系统完成,也可以由一个或一组人完成,还可以是由一个或多个人与软件系统协作完成。任务的触发顺序和触发条件用来定义并实现任务的触发、任务的同步和信息流(数据流)的传递。
树前序遍历工作流
是一种具有树状数据结构的任务节点,按照前序遍历的熟悉怒, 组成的工作流。
每个树节点的执行,可以含有异步操作, 或者定时触发下一步操作。
每个节点任务执行完毕后, 下一个被执行任务节点, 是本节点的前序遍历中的下一个任务节点。
每个任务节点上的等待的动作, 就是异步特征。
技术探查
promise
首先技术上js提供promise能够很好处理, 每个任务节点的异步执行 或者 定制触发下一个任务节点的情况。
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise
http://www.infoq.com/cn/news/2011/09/js-promise
但是其不能确定工作流的顺序, 还是要依赖树装结构遍历实现。
按照promise标准实现的库 q.js
https://github.com/kriskowal/q/blob/v1/examples/all.js
https://github.com/kriskowal/q
casper
基于phantomjs的封装, 支持promise风格访问网页, 比phantomjs原生提供的接口api,有些进步。
但是对于最简单的线性工作流程(两个任务), 也需要嵌套实现 then 中 thenOpen。
9 casper.start('http://google.com/', function() {
10 // 通过google表单搜索“CasperJS”关键字
11 this.fill('form[action="/search"]', { q: 'CasperJS' }, true);
12 });
13 casper.then(function() {
14 // 聚合“CasperJS”关键字搜索结果
15 links = this.evaluate(getLinks);
16 for (var i = 0; i < links.length; i++) {
17 casper.thenOpen(links[i]);
18 casper.then(function() {
19 var isFound = this.evaluate(function() {
20 return document.querySelector('html').textContent.indexOf('CasperJS') >= 0;
21 });
22 console.log('CasperJS is found on ' + links[i] + ':' + isFound);
23 });
24 }
25 });
26 casper.run();
knysa
此工具避免了caperjs的对工作流的支持缺陷, 避免的嵌套, 支持了使用for while 等控制结构, 控制异步任务流的执行, 见
http://www.infoq.com/cn/articles/knysa-phantomjs-async-await?utm_campaign=rightbar_v2&utm_source=infoq&utm_medium=articles_link&utm_content=link_text
可以使用同步的代码风格书写出, 异步任务流的流程控制。
对于任务流控制的中, 避免了 原生js的回调陷阱 和 caperjs的嵌套书写 缺陷。
但是此框架是基于phantomjs的深度封装, 只想部分引入此特性,对于现有已经熟悉或者已有项目积累的情况, 整体替换此框架不合适。
demo:
kflow.knysa_open('http://google.com/');
10 kflow.knysa_fill('form[action="/search"]', { q: 'CasperJS' });
11 links = kflow.evaluate(getLinks);
12 i = -1;
13 while (++i < links.length) {
14 kflow.knysa_open(links[i]);
15 isFound = kflow.evaluate(function() {
16 return document.querySelector('html').textContent.indexOf('CasperJS') >= 0;
17 });
18 console.log('CasperJS is found on ' + links[i] + ':' + isFound);
19 }
20 phantom.exit();
ES6 async 和 await
js语言标准提供的新特性, 支持使用同步编码的风格写异步流程。
但是需要新的运行环境支持。
http://blog.csdn.net/exialym/article/details/52857171
demo:
function timeout(data, ms) {
return new Promise((resolve) => {
setTimeout(function(){
resolve(data);
}, ms);
});
}
async function asyncPrint(value, ms) {
//timeout会返回一个promise对象
//await会等待这个对象中的resolve方法执行
//并用其参数当做自己的返回值
//值得注意的是await命令后面的Promise对象
//运行结果可能是rejected
//所以最好把await命令放在try...catch代码块中
//或者使用catch方法
var a = await timeout(value,ms)
.catch(function (err) {
console.log(err);
});
console.log('a:'+a);
return 'async over'
}
asyncPrint('hello world', 5000).then(v => console.log(v));
console.log('after async');
//after async
//a:hello world
//async over
Wind库:
http://www.infoq.com/cn/articles/jscex-javascript-asynchronous-programming
提供 await async类似功能。
赵jeffery提供的库,兼容低版本浏览器(环境不用考虑), 造福码农,扬中国码农威名。
https://github.com/JeffreyZhao/wind
demo
// 异步的比较操作
var compareAsync = eval(Jscex.compile("async", function (x, y) {
$await(Jscex.Async.sleep(10)); // 等待10毫秒
return x - y;
}));
// 异步的交换操作
var swapAsync = eval(Jscex.compile("async", function (array, i, j) {
var t = array[i];
array[i] = array[j];
array[j] = t;
repaint(array); // 重绘
$await(Jscex.Async.sleep(20)); // 等待20毫秒
}));
// 异步的冒泡排序
var bubbleSortAsync = eval(Jscex.compile("async", function (array) {
for (var i = 0; i < array.length; i++) {
for (var j = 0; j < array.length - i; j++) {
// 执行异步的比较操作
var r = $await(compareAsync(array[j], array[j + 1]));
if (r > 0) {
// 执行异步的交换操作
$await(swapAsync(array, j, j + 1));
}
}
}
}));
// 调用
var array = ...; // 初始化数组
bubbleSortAsync(array).start();
Wind实现树遍历业务流
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Hanoi - Wind.js Samples</title>
<script src="../../../src/wind-core.js"></script>
<script src="../../../src/wind-compiler.js"></script>
<script src="../../../src/wind-builderbase.js"></script>
<script src="../../../src/wind-async.js"></script>
</head>
<body>
<script>
var treejson = {value:"root", children:[
{value:"childone"}, {value:"childtwo"}
]};
// 树前序遍历方法
function treeTraverse_preOder (treejson) {
if ( !treejson )
{
return;
}
console.log("node value ="+treejson.value);
if ( !treejson.children )
{
return
}
for (var i = 0; i < treejson.children.length; i++) {
var child = treejson.children[i];
treeTraverse_preOder(child)
}
}
treeTraverse_preOder(treejson)
// 下面使用wind执行时间空格前序遍历
// 异步的输出操作
var printAsync = eval(Wind.compile("async", function (treejson) {
console.log(treejson.value)
$await(Wind.Async.sleep(2000)); // 等待2秒
return true;
}));
// 异步的树遍历操作
var treeTraverseAsync = eval(Wind.compile("async", function (treejson) {
if ( !treejson )
{
return;
}
$await(printAsync(treejson));
if ( !treejson.children )
{
return
}
for (var i = 0; i < treejson.children.length; i++) {
var child = treejson.children[i];
$await(treeTraverseAsync(child));
}
}));
treeTraverseAsync(treejson).start();
</script>
</body>
</html>