功能背景
大部网站都有菜单功能, 每个页面都有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>