树遍历工作流方法研究

时间:2021-12-27 00:07:08

功能背景

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