大部網站都有菜單功能, 每一個頁面都有ajax更新頁數據請求, 如何將頁面上的全部菜單的頁面都保存下來本地PC? 後續能夠在本地PC上瀏覽頁面。javascript
前面一個文章 利用phantomjs能夠抓取單個頁面的並保存到PC, 能夠本地瀏覽。html
http://www.cnblogs.com/lightsong/p/5971701.htmljava
每一個頁面ajax數據更新, 須要等待若干時間, 因此在將頁面保存PC的時刻, 須要在ajax數據返回以後,node
故須要在phantomjs代碼中須要控制等待足夠時間, 以確保頁面的ajax更新執行完畢。 通常頁面這個等待時間並不算長, 1-2完成。git
可是對於樹狀的菜單結構, 如何保證一個菜單都被訪問過, 頁面都被保存到PC, 而後再訪問第二個菜單, 執行第二個菜單的訪問和保存工做?github
若是每一個菜單訪問沒有異步問題, 直接使用菜單的樹的遍歷機制便可。 可是樹的遍歷,每一個節點訪問都要考慮異步性訪問, 正常的遍歷就無能爲力了。ajax
事實上, 這種異步業務執行的 流程控制, 是工做流研究的範疇。json
http://blog.csdn.net/wuluopiaoxue/article/details/6522908api
工做流是將一組任務組織起來完成某個經營過程。在工做流中定義了任務的觸發順序和觸發條件。每一個任務能夠由一個或多個軟件系統完成,也能夠由一個或一組人完成,還能夠是由一個或多我的與軟件系統協做完成。任務的觸發順序和觸發條件用來定義並實現任務的觸發、任務的同步和信息流(數據流)的傳遞。數組
是一種具備樹狀數據結構的任務節點,按照前序遍歷的熟悉怒, 組成的工做流。
每一個樹節點的執行,能夠含有異步操做, 或者定時觸發下一步操做。
每一個節點任務執行完畢後, 下一個被執行任務節點, 是本節點的前序遍歷中的下一個任務節點。
每一個任務節點上的等待的動做, 就是異步特徵。
首先技術上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
基於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();
此工具避免了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();
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
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();
<!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>