Node.js Async套件
關於Node.js的 Async ,針對會使用這個套件的原因大概描述一下:
1.依序執行function:如果碰到需要執行多個function,且每個function之間有前後關係時,如果不使用Promise語法或Async套件,你就只能一層又一層的寫下去,類似下面這樣
2.執行迴圈辦法陣列物件:
比如你有一個物件陣列,要一一比對每個值,挑選出需要的物件
上面語法看起來似乎沒什麼問題,不過重點是array的forEach(),或者是map()這些功能相近的function,都是同步(Synchronous)的,也就是要等到forEach()執行完才會繼續執行下一行指令,而Node.js是單執行序的,也就是說所有如果每個array的元素需要運算很久或array中包含太多的項目,就可能造成其他需求等待的時間過長。
使用套件管理工具npm或yarn安裝,功能包含兩大類型,一類是用於流程控制(control flow),另一類則是處理集合操作(collections),剛好對應上面兩個問題,下面會各提出一個函式來介紹。
1.流程控制:多個函式需依序執行時
當你需要依序執行多個function時,可以使用async的waterfall,傳入要依序執行的function陣列與第二個參數callBack的函式,async會依序執行陣列中的函式,一個完成後才會呼叫下一個,直到每個都執行完畢,當任一個於回呼時傳入錯誤,則callBack會被觸發,並收到拋出的錯誤,不然只有最後一個函式執行回呼時,才會進入callBack,請參考範例:
利用callback傳入第一個參數來結束程序的機制,某些時候並不是因為發生錯誤,可能只是因為工作已經處理完,可以跳過後面的function,所以打算結束程序,為了跟發生錯誤時做區別,可以如下面這樣:
async除了依序執行的waterfall以外,也有不管function執行順序的parallel或需判斷條件執行的各類與流程控制有關的function,有機會再介紹。
2.集合處理(collections)
像是Array的map、forEach,只是要採用異步方式,避免處理多筆數的集合時,佔用處理序太久,最簡單的就是each函式,參考範例如下:
可以看到完成雖然a先執行,可是b、c、d在a還未執行完之前就依序執行並完成了。
那如果有需要一個處理完成才處理下一個值時該怎麼辦呢?async提供了eachSeries來處理這個問題,eachSeries只會有一個異步程序在執行,所以可以控制其按順序一個完成後才執行下一個,下面範例是只修改each為eachSeries,執行程式可以明顯看到第二項b會等到end a列印出來後才執行
請務必自己嘗試看看喔,會有比較直觀的認識。
由文件中還可以看到另一個eachLimit函式,其差別在於可以指定同時有多少個異步程序在執行,當然node是單執行序的,這邊所要表示的是說,同時允許多少個執行去排隊等待,牽扯到node基本認識的部份有機會再談,總之先想成eachLimit可以設定同一個時間允許幾個項目執行,eachSeries就像是指定Limit值為1的eachLimit一樣。
async的集合處理函式還有很多類型,比如會有回傳值的map等,基本上也都提供了Limit與Series兩個差異化的函式,如mapLimit與mapSeries,意義上與剛剛介紹的相同,大家可以自己嘗試看看。
一、狀況
寫作Node.js專案時,由於單執行序與事件驅動的特性,勢必須要面對依序執行函式,或要拜訪每個陣列元素又要避免佔用執行序太久的狀況,先說明一下案例1.依序執行function:如果碰到需要執行多個function,且每個function之間有前後關係時,如果不使用Promise語法或Async套件,你就只能一層又一層的寫下去,類似下面這樣
function a(callBack){
return callBack(null , "a done");
}
function b(callBack){
return callBack(null , "b done");
}
function c(callBack){
return callBack(null , "c done");
}
a(function(error , rtn){
console.log(rtn);
b(function(error , rtn){
console.log(rtn);
c(function(error , rtn){
console.log(rtn);
});
});
});
//output:
a done
b done
c done
如同上面範例看到的,為了保證依序執行,必須這樣一層一層的寫下去,請想想一下四層或五層,中間再加上一些邏輯判斷,有些要執行有些又不需要執行等,原始碼會變得很複雜難以閱讀跟維護。return callBack(null , "a done");
}
function b(callBack){
return callBack(null , "b done");
}
function c(callBack){
return callBack(null , "c done");
}
a(function(error , rtn){
console.log(rtn);
b(function(error , rtn){
console.log(rtn);
c(function(error , rtn){
console.log(rtn);
});
});
});
//output:
a done
b done
c done
2.執行迴圈辦法陣列物件:
比如你有一個物件陣列,要一一比對每個值,挑選出需要的物件
var array1 = [{ type:1,name:"p1"} , { type:2,name:"p2"} , { type:1,name:"p3"}];
var array2 = [];
array1.forEach(function(item){
if(item.type === 1){
array2.push(item);
}
});
console.log(array2);
//output
[ { type: 1, name: 'p1' }, { type: 1, name: 'p3' } ]
var array2 = [];
array1.forEach(function(item){
if(item.type === 1){
array2.push(item);
}
});
console.log(array2);
//output
[ { type: 1, name: 'p1' }, { type: 1, name: 'p3' } ]
上面語法看起來似乎沒什麼問題,不過重點是array的forEach(),或者是map()這些功能相近的function,都是同步(Synchronous)的,也就是要等到forEach()執行完才會繼續執行下一行指令,而Node.js是單執行序的,也就是說所有如果每個array的元素需要運算很久或array中包含太多的項目,就可能造成其他需求等待的時間過長。
二、來說說Async
關於Async,他的說明文件連結在這邊:使用套件管理工具npm或yarn安裝,功能包含兩大類型,一類是用於流程控制(control flow),另一類則是處理集合操作(collections),剛好對應上面兩個問題,下面會各提出一個函式來介紹。
1.流程控制:多個函式需依序執行時
當你需要依序執行多個function時,可以使用async的waterfall,傳入要依序執行的function陣列與第二個參數callBack的函式,async會依序執行陣列中的函式,一個完成後才會呼叫下一個,直到每個都執行完畢,當任一個於回呼時傳入錯誤,則callBack會被觸發,並收到拋出的錯誤,不然只有最後一個函式執行回呼時,才會進入callBack,請參考範例:
var async = require("async");
async.waterfall([
function(callback) {
callback(null, 'one', 'two');
},
function(arg1, arg2, callback) {
// arg1 now equals 'one' and arg2 now equals 'two'
console.log(arg1+"/"+arg2);
callback(null, 'three');
},
function(arg1, callback) {
// arg1 now equals 'three'
console.log(arg1);
callback(null, 'done');
}
], function (err, result) {
// result now equals 'done'
console.log(result);
});
//output
one/two
three
done
參考上面範例,會看到將參數一個個往後傳遞,一直到最後一個函式呼叫callback時,觸發waterfall的第二個參數並取得err與result,中間任何function如果於callback時第一個參數傳入任何值,整個程序會馬上結束。async.waterfall([
function(callback) {
callback(null, 'one', 'two');
},
function(arg1, arg2, callback) {
// arg1 now equals 'one' and arg2 now equals 'two'
console.log(arg1+"/"+arg2);
callback(null, 'three');
},
function(arg1, callback) {
// arg1 now equals 'three'
console.log(arg1);
callback(null, 'done');
}
], function (err, result) {
// result now equals 'done'
console.log(result);
});
//output
one/two
three
done
利用callback傳入第一個參數來結束程序的機制,某些時候並不是因為發生錯誤,可能只是因為工作已經處理完,可以跳過後面的function,所以打算結束程序,為了跟發生錯誤時做區別,可以如下面這樣:
var async = require("async");
function check(a1 , a2){
async.waterfall([
function(callback) {
if(!a1 || !a2){
return callback(new Error("需要傳入a1與a2"));
}
if(a1===a2){
//提前結束
return callback(true);
}
return callback();
},
function(callback) {
return callback(null , "執行到第二個function了");
}
], function (done, result) {
if(done === true){
return console.log("第一個function就正常跳出,無錯誤發生");
}
else if(done instanceof Error){
//done為一個Error物件
return console.log(done.message);
}
// result now equals 'done'
return console.log(result);
});
}
check();
check(1,1);
check(1,2);
//output
需要傳入a1與a2
第一個function就正常跳出,無錯誤發生
執行到第二個function了
就像範例看到的,於async.waterfall的callback中,透過檢查第一個傳入參數的值或類型,判斷程序是正常執行完畢或是因為意外而結束。function check(a1 , a2){
async.waterfall([
function(callback) {
if(!a1 || !a2){
return callback(new Error("需要傳入a1與a2"));
}
if(a1===a2){
//提前結束
return callback(true);
}
return callback();
},
function(callback) {
return callback(null , "執行到第二個function了");
}
], function (done, result) {
if(done === true){
return console.log("第一個function就正常跳出,無錯誤發生");
}
else if(done instanceof Error){
//done為一個Error物件
return console.log(done.message);
}
// result now equals 'done'
return console.log(result);
});
}
check();
check(1,1);
check(1,2);
//output
需要傳入a1與a2
第一個function就正常跳出,無錯誤發生
執行到第二個function了
async除了依序執行的waterfall以外,也有不管function執行順序的parallel或需判斷條件執行的各類與流程控制有關的function,有機會再介紹。
2.集合處理(collections)
像是Array的map、forEach,只是要採用異步方式,避免處理多筆數的集合時,佔用處理序太久,最簡單的就是each函式,參考範例如下:
var async = require("async");
var array1 = ["a","b","c","d"];
async.each(array1 ,
function(value , cb){
console.log(value);
return cb();
},
function(error){
if(error){
//發生錯誤
}
else{
//完成
console.log("done")
}
})
//output
a
b
c
d
done
需注意的是each會依順序執行,卻不保證會依據順序結束,下面是故意讓第一個參數較晚結束的狀況var array1 = ["a","b","c","d"];
async.each(array1 ,
function(value , cb){
console.log(value);
return cb();
},
function(error){
if(error){
//發生錯誤
}
else{
//完成
console.log("done")
}
})
//output
a
b
c
d
done
var async = require("async");
var array1 = ["a","b","c","d"];
async.each(array1 ,
function(value , cb){
console.log("start "+value);
if(value === "a"){
wait(1000 , function(){
console.log("end "+value);
return cb();
});
}
else{
console.log("end "+value);
return cb();
}
},
function(error){
if(error){
//發生錯誤
}
else{
//完成
console.log("done")
}
});
function wait(value , callback){
setTimeout(callback, value);
}
//output
start a
start b
end b
start c
end c
start d
end d
end a
done
var array1 = ["a","b","c","d"];
async.each(array1 ,
function(value , cb){
console.log("start "+value);
if(value === "a"){
wait(1000 , function(){
console.log("end "+value);
return cb();
});
}
else{
console.log("end "+value);
return cb();
}
},
function(error){
if(error){
//發生錯誤
}
else{
//完成
console.log("done")
}
});
function wait(value , callback){
setTimeout(callback, value);
}
//output
start a
start b
end b
start c
end c
start d
end d
end a
done
可以看到完成雖然a先執行,可是b、c、d在a還未執行完之前就依序執行並完成了。
那如果有需要一個處理完成才處理下一個值時該怎麼辦呢?async提供了eachSeries來處理這個問題,eachSeries只會有一個異步程序在執行,所以可以控制其按順序一個完成後才執行下一個,下面範例是只修改each為eachSeries,執行程式可以明顯看到第二項b會等到end a列印出來後才執行
var async = require("async");
var array1 = ["a","b","c","d"];
async.eachSeries(array1 ,
function(value , cb){
console.log("start "+value);
if(value === "a"){
wait(1000 , function(){
console.log("end "+value);
return cb();
});
}
else{
console.log("end "+value);
return cb();
}
},
function(error){
if(error){
//發生錯誤
}
else{
//完成
console.log("done")
}
});
function wait(value , callback){
setTimeout(callback, value);
}
//output
start a
end a
start b
end b
start c
end c
start d
end d
done
var array1 = ["a","b","c","d"];
async.eachSeries(array1 ,
function(value , cb){
console.log("start "+value);
if(value === "a"){
wait(1000 , function(){
console.log("end "+value);
return cb();
});
}
else{
console.log("end "+value);
return cb();
}
},
function(error){
if(error){
//發生錯誤
}
else{
//完成
console.log("done")
}
});
function wait(value , callback){
setTimeout(callback, value);
}
//output
start a
end a
start b
end b
start c
end c
start d
end d
done
請務必自己嘗試看看喔,會有比較直觀的認識。
由文件中還可以看到另一個eachLimit函式,其差別在於可以指定同時有多少個異步程序在執行,當然node是單執行序的,這邊所要表示的是說,同時允許多少個執行去排隊等待,牽扯到node基本認識的部份有機會再談,總之先想成eachLimit可以設定同一個時間允許幾個項目執行,eachSeries就像是指定Limit值為1的eachLimit一樣。
async的集合處理函式還有很多類型,比如會有回傳值的map等,基本上也都提供了Limit與Series兩個差異化的函式,如mapLimit與mapSeries,意義上與剛剛介紹的相同,大家可以自己嘗試看看。
留言