1. 程式人生 > >控制異步回調利器 - async 串行series,並行parallel,智能控制auto簡介

控制異步回調利器 - async 串行series,並行parallel,智能控制auto簡介

一個 回調函數 錯誤信息 fan har require ner module 邏輯

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簡介