树节点遍历-异步工作流方法研究

功能背景

大部网站都有菜单功能, 每个页面都有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>
原文地址:https://www.cnblogs.com/lightsong/p/6036477.html