控制異步回調利器 - async 串行series,並行parallel,智能控制auto簡介
async 作為大名鼎鼎的異步控制流程包,在npmjs.org 排名穩居前五,目前已經逐漸形成node.js下控制異步流程的一個規範.async成為異步編碼流程控制的老大哥絕非偶然,它不僅使用方便,文檔完善,把你雜亂無章的代碼結構化,生辰嵌套的回掉清晰化.
async 提供的api包括三個部分:
(1)流程控制 常見的幾種流程控制.
(2)集合處理 異步操作處理集合中的數據.
(3)工具類 .
github 開源地址: https://github.com/caolan/async
安裝方法: npm install async
使用方法: var async=require(‘async‘);
串行且無關聯
場景:提取某學校大三學生的學生信息(假設每個班級存儲在一個獨立的數據表裏)
分析:每個班級的學生之間是無關聯的,假設共有3個班級,我們需要遍歷3個表,把提取出的學生信息返回客戶端一個json,如下
{ "1班":[{name:"張三",age:"21",class:"1班"},......(省略N個學生)] "2班":[{name:"李四",age:"22",class:"2班"},......(省略N個學生)] "3班":[{name:"王五",age:"22",class:"3班"},......(省略N個學生)] }
如果不使用 async
var class=require(‘./module/class‘); export.pageStudent=function(req,res){ var rtnJson={}; class.getStudent(‘1班‘,function(error,oneResult){ if(!error&&oneResult){ rtnJson[‘1班‘]=oneResult; class.getStudent(‘2班‘,function(error,twoResult){ if(!error&&twoResult){ rtnJson[‘2班‘]=twoResult; class.getStudent(‘3班‘,function(error,threeResult){ if(!error&&threeResult){ rtnJson[‘3班‘]=threeResult; //3個班級全部獲取完成 res.render(‘./veiw/pageStudent‘,{students:rtnJson}); }else{ res.render(‘./veiw/pageStudent‘,{students:rtnJson}); } }); }else{ res.render(‘./veiw/pageStudent‘,{students:rtnJson}); } }); }else{ res.render(‘./veiw/pageStudent‘,{students:rtnJson}); } }); }
如果某個年級有8個班級,那這樣嵌套下去會是什麽樣的結果.....如果有一天修改邏輯,自己回頭查看自己的代碼也是一頭霧水,不知該從哪下手.
利用 async series 控制串行無關聯流程,用法如下:
async.series({ flag1:function(done){ //flag1 是一個流程標識,用戶自定義 //邏輯處理 done(null,返回結果)// 第一個參數是異常錯誤,第二個參數的返回結果 }, flag2:function(done){ //邏輯處理 done(‘error info‘,null) //如果返回錯誤信息, //下面的流程控制將會被中斷,直接跳到最後結果函數 }, },function(error,result){ //最後結果 //result是返回結果總集,包含了所有的流程控制 , //result.flag1 可以獲取標識1中處理的結果 });
所以我們用 series 來串行控制一下這個流程:
async.series({ oneClass:function(done){ class.getStudent(‘1班‘,function(error,oneResult){ if(!error) done(null,oneResult); else done(error,null); }); }, twoClass:function(done){ class.getStudent(‘2班‘,function(error,twoResult){ if(!error) done(null,twoResult); else done(error,null); } }, threeClass:function(done){ class.getStudent(‘3班‘,function(error,threeResult){ if(!error) done(null,threeResult); else done(error,null); } } },function(error,result){ if(!error) callback(null,result); else callback(error,null); });
上面是一個標準的串行流程,代碼可讀性很強, 容易維護,但是這種流程只適合按順序執行且每一步沒有關聯
如果你的業務邏輯是根本不需要按順序執行的,比如獲取不同班級的信息,其實先獲取1班和先獲取3班是一樣的,只要最後結果保證3個班的人員信息都獲取成功即可.所以這裏用series 是一種錯誤,反而和 node.js 的異步IO相互矛盾.應該用 並行且無關聯的控制流程.
串行無關聯模式要求每一步執行成功後才能執行下一步流程.所以是一個同步編程思想.看我寫的一個demo 的執行時間
//測試代碼,沒有任何邏輯處理,按4步執行,最後看執行時間. console.time(‘series‘); var async = require(‘async‘); async.series({ one: function (done) { //處理邏輯 done(null, ‘one‘); }, two: function (done) { //處理邏輯 done(null, ‘tow‘); }, three: function (done) { //處理邏輯 done(null, ‘three‘); }, four: function (done) { //處理邏輯 done(null, ‘four‘); } }, function (error, result) { console.log(‘one:‘, result.one); console.log(‘two:‘, result.two); console.log(‘three:‘, result.three); console.log(‘four:‘, result.four); console.timeEnd(‘series‘); })
分4步串行控制,最後耗時 14毫秒
並行且無關聯
場景如上,獲取4個班級學生信息.
async 裏的提供的並行無關聯 api 是 parallel
parallel 的原理是同時並行處理每一個流程,最後匯總結果,如果某一個流程出錯就退出.把獲取班級成員信息的代碼用 parallel 來實現如下
async.parallel({ oneClass:function(done){ class.getStudent(‘1班‘,function(error,oneResult){ if(!error) done(null,oneResult); else done(error,null); }); }, twoClass:function(done){ class.getStudent(‘2班‘,function(error,twoResult){ if(!error) done(null,twoResult); else done(error,null); } }, threeClass:function(done){ class.getStudent(‘3班‘,function(error,threeResult){ if(!error) done(null,threeResult); else done(error,null); } } },function(error,result){ if(!error) callback(null,result); else callback(error,null); });
看上去和串行無關聯的代碼只是換了一個關鍵詞而已,確實是這樣,他們接收的參數形式完全一致.但是實現方法卻完全不同,還是用demo數據來測試
var async = require(‘async‘); console.time(‘parallel‘); async.parallel({ one: function (done) { //處理邏輯 done(null, ‘one‘); }, two: function (done) { //處理邏輯 done(null, ‘tow‘); }, three: function (done) { //處理邏輯 done(null, ‘three‘); }, four: function (done) { //處理邏輯 done(null, ‘four‘); } }, function (error, result) { console.log(‘one:‘, result.one); console.log(‘two:‘, result.two); console.log(‘three:‘, result.three); console.log(‘four:‘, result.four); console.timeEnd(‘parallel‘); })
一樣是4個控制流程,並行模式下耗時 3毫秒,大約接近串行模式耗時1/5 (此比例是單樣本統計,只供參考)
串行且有關聯
場景:打開微博首頁需要加載 微博個人信息,微博分組信息,微博分組粉絲信息 這裏不考慮ajax 異步拉取每個模塊.如果我們用ejs來渲染,需要發送給前端頁面一個這樣的數據結構(簡單的模擬數據)
{ userInfo:{userID:10001,totalNum:368,fans:562,follow:369} group:[{groupID:100,groupName:"粉絲"},{groupID:200,groupName:"同事"}...], fansGroup:{"粉絲":[{nickName:‘aa‘,age:20},{nickName:‘bb‘,age:22}....]} }
上面的信息取自3個不同的表,但是每一個流程都和上一個流程有關系,也就是說,如果拿到用戶信息後,根據用戶ID 獲取此微博用戶的分組,根據分組ID獲取每個組裏面的粉絲.一環扣一環,希望流程按順序執行,且每一步邏輯控制都能由上一步得到的結果來做條件.
var userInfo=require(‘./lib/module/userInfo‘); var group=require(‘./lib/module/group‘); var groupFans=require(‘./lib/module/groupFans‘); //傳統嵌套代碼如下 export.pageIndex=function(req,res){ userInfo.get(userEmail,passWord,function(error,userInfo){ group.get(userInfo.userID,function(error,groupList){ var idx=0,fansList=[]; for(var i=0;i<groupList.length;i++){ groupFans.get(groupList[idx++],function(error,fansInfo){ fansList.push(fansInfo); if(idx==groupList.length){ callback(null,{userInfo:userInfo,group:groupList,fansGroup:fansList}); } }) } }); }); }
上面的代碼互相牽扯關系,每一步的邏輯運算都需要上一步的結果來支持,我們假設每一步都運行正確,沒有對error 進行判斷.
最後因為要遍歷數組中元素,然後把每個元素對應的分組成員都組合起來,我們用到了數據索引計數器 idx,上面的代碼看似沒有問題,但是索引計數器非常不好控制,稍有差錯可能會不運行.
anync 的waterfall 適合上面的場景.
waterfall 每一步執行時需要由上一步執行的結果當做參數.所以每一步必須串行等待.事例代碼如下:
console.time(‘waterfall‘); async.waterfall([ function (done) { done(null, ‘one‘); }, function (onearg, done) { done(null, onearg + ‘| two‘); }, function (twoarg, done) { done(null, twoarg + ‘| three‘); }, function (threearg, done) { done(null, threearg + ‘| four‘); } ], function (error, result) { console.log(result); console.timeEnd(‘waterfall‘); })
上面調用 waterfall 函數時傳入一個數組,數組總的每一個元素就是一個串行控制節點,每一個節點執行必須保證上一節點已經執行完成且拿到結果.這樣將結果傳入下一個控制節點作為參數來運行.
參數數組第一個元素回調函數 done(null,‘one‘) -->null 說明執行沒有錯誤, ‘one‘ 是第一個節點運行返回的結果(這個結果將會傳入第二個控制流程來作為參數).....這樣以此類推,最後一個元素(第四個)返回的結果應該是 ‘one|tow|three|four ‘ 這個字符串,也就是 result 打印出的內容.
註意:
async 提供的api默認支持多種傳遞參數的寫法,我個人比較喜歡用對象表示法來傳遞( json格式) 但是waterfall 這個api很特殊,不支持對象參數,如果你用下面的錯誤代碼來調用 waterfall 的話,你不會拿到運行結果.
//此調用方法是錯誤的!!! console.time(‘waterfall‘); async.waterfall({ one: function (done) { //處理邏輯 done(null, ‘one‘); }, two: function (onearg, done) { //處理邏輯 console.log(‘-----‘, onearg); done(null, onearg + ‘two‘); }, three: function (twoarg, done) { //處理邏輯 done(null, twoarg + ‘three‘); }, four: function (threearg, done) { //處理邏輯 done(null, threearg + ‘four‘); } }, function (error, result) { console.log(result); console.timeEnd(‘waterfall‘); })
最後由於拿不到返回結果,調用結果為 undefined
智能控制 auto
如果你的邏輯代碼很繁瑣,涉及到很多的流程控制,但是部分流程是相互依賴的,部分又是無依賴關系而並行獨立的.這時 auto 這個智能控制流程 api 再適合你不過了. 後續補充....
本文由 一介布衣 創作,采用 知識共享署名 3.0 中國大陸許可協議。
可自由轉載、引用,但需署名作者且註明文章出處。
本站部署於「UCloud雲計算」。
右側微信掃描 「打賞」,感謝支持!
控制異步回調利器 - async 串行series,並行parallel,智能控制auto簡介