註:提及單執行緒的文章 --- [Nodejs]關於、介紹和筆記
此篇主要介紹非同步處理跟管理方法,大致上就是async和promise這兩種非同步用法,當然,絕對不是只有這兩種方法可以使用,不過大部分 npm社群網站中的套件都會優先符合這兩者。而Shach本身是習慣使用async,所以此篇還是主要以async為主,如果有哪邊寫錯還請高人指點迷津。
註:node js在v4版本後已經把promise納入核心模組中。
環境:Linux/Windows
版本:v4.X LTE
主要套件:async
輔助套件:
fs - 講解用,實際上使用並不會用到
前言
一般我們在寫nodejs的語言時,為了程序更像是多執行緒的流程,我們會將function切割開來丟到task queue中,並且盡量減少單一function的處理量,儘管看起來還是一條線的執行方式,不過真正在執行的時候,會因為function不斷切換的原因使的每個function在排隊的時候都可以更快的被執行到,而感受更為深刻。
但這時候遇到的問題就是如果function進入的一個無法取得回傳(return)的狀況下,要怎麼樣讓程式能夠繼續順利的進行呢?
舉例
example
var fs = require("fs");
var file_str = fs.readFile("./foo.txt");
console.log(file_str); // undefined
上面的範例是錯的, fs是官方提供的套件之一,使用fs.readFile時會調用非同步的方法,將執行的內容跑入你抓不到的範圍內,理所當然他的回傳值也就不會是在readFile這個function上。
example
var fs = require("fs");
var file_str = fs.readFile("./foo.txt", function(err,result){
if(err) console.log(err);
else console.log(result); // foo.txt content
});
以上才是正確的使用方法,給予一個function告知readFile執行結束後,將值傳入並執行該function。
名詞、變數
callback/cb - 回呼函數,在呼叫非同步函數時,傳入的function,以便非同步函數結束時將值傳入並執行
async - 非同步 或指async套件
sync - 同步
foo - 純粹就是個可以被取代的名字而已,你也可以把他改成qoo
argument - 函數所接收的參數
key word:
argument s - function作用域內部自帶變數,代表的是所收到的參數陣列。
註:arguments實際上並非是array,詳細請閱讀 [Nodejs]函數/類別(function)
回呼函數(callback/cb)
首先你必須要知道的事情是,回呼函數有個默認的function model,即使在大部分的套件上都會是一樣的做法。
function(err, argument1, argument2, ...){
// your code here
}
在這個function model上要注意的是,第一個被傳入的參數永遠是error,如果該非同步函數順利執行成功沒有錯誤,那麼第一個參數就會是undefined/null,而後第二個參數以後則為該非同步函數真正回傳的值。
--------------------------------------------------------------------------------------------------------------------------
實作非同步函數
var append_file_to_file = function(file1, file2, callback) {
var file_combine = file1 + file2;
// 執行非同步函數
fs.writeFile("combine_result", file_combine, function(err, result) {
// function執行結束,傳入結果並執行callback。
callback(err, result);
});
};
實作非同步函數時,請記得將callback定義在函數接收參數的最後一個,之後維護或是替function增加參數時,才不會有問題。所以我們也可以改成:
var append_file_to_file = function() {
var callback = typeof arguments[arguments.length - 1] === "function" ? arguments[arguments.length - 1] : function() {};
var file_combine = "";
for (var i = 0; i < (arguments.length - 1); i++) {
file_combine += arguments[i];
}
// 執行非同步函數
fs.writeFile("combine_result", file_combine, function(err, result) {
// function執行結束,傳入結果並執行callback。
callback(err, result);
});
};
var div = function(num1, num2) {
var callback = typeof arguments[arguments.length - 1] === "function" ? arguments[arguments.length - 1] : function() {};
// 只是為了實現你不知道他什麼時候結束而已,沒有特別意義。
var rand_num_1_to_1000_ms = (Math.floor(Math.random() * 1000) + 1);
setTimeout(function() {
if (!num2) {
// 如果發生錯誤,則將錯誤傳入callback第一個參數
callback("second argument must to be a number larger then 0");
} else {
// 正確執行時,將結果放於第二個參數之後
callback(undefined, num1 / num2);
}
}, rand_num_1_to_1000_ms);
};
執行非同步函數
延續上一個主題實作的function div
div(1,0,function(err,result){
if(err) console.log(err);
else console.log(result);
});
/**
* output:
* "second argument must to be a number larger then 0"
*/
多層運用
div(1, 0, function(err, result) {
if (err) console.log(err);
else console.log(result);
div(100, 10, function(err, result) {
if (err) console.log(err);
else console.log(result);
});
});
/**
* output:
* "second argument must to be a number larger then 0"
* 10
*/
就是這樣如此簡單,不過你可能會注意到,你已經開始出現所謂的 金字塔、callback地獄,不斷的使用非同步雖然會提升效能,但也大大增加了程式不易閱讀性。
--------------------------------------------------------------------------------------------------------------------------
本文重點,async的使用,可以增加非同步函數的管理維護和易讀性。
[Nodejs]關於、介紹和筆記裡面有提到,單執行緒的效能在於可以將閒置的時間拿去處理其他的任務,以任務分割時間處理達到多工的效果,因此將function輕量化是一個很重要的課題,Shach自己也會把冗長的程式碼分割成很多個小function,然後替每個function寫上註解以便管理不同的小區塊。用async時並沒有強制規定裡頭一定要執行的是非同步函數,只要記得告訴async你的function什麼時候結束就可以了。
以下介紹幾種常用的function,剩下的請拜讀 官方網站。
在使用到async的地方引用async模組,並賦予到變數上。
var async = reuiqre("async");
並列執行(async.series)
async.series([function array/object], final_callback);
這裡特別注意,裡頭所實作的function都會收到一個來自async函數傳入的callback,讓async知道你的function已經結束了,並且接收你的值後在final_callback還給你。
而callback參數傳入的位置一定會是在function所接收的參數最後一個,所以下面實作直接以此為原則來取得callback。
async.series([
function() {
var callback = typeof arguments[arguments.length - 1] === "function" ? arguments[arguments.length - 1] : function() {};
div(1, 0, callback);
},
function() {
var callback = typeof arguments[arguments.length - 1] === "function" ? arguments[arguments.length - 1] : function() {};
div(100, 10, callback);
}
], function(err, result_arr) {
if (err) console.log(err);
else console.log(result_arr);
});
/**
* output:
* "second argument must to be a number larger then 0"
*/
因為function array第一個function傳出了錯誤,所以直接呼叫final_callback。
async.series([
function() {
var callback = typeof arguments[arguments.length - 1] === "function" ? arguments[arguments.length - 1] : function() {};
div(16, 4, callback);
},
function() {
var callback = typeof arguments[arguments.length - 1] === "function" ? arguments[arguments.length - 1] : function() {};
div(100, 10, callback);
}
], function(err, result_arr) {
if (err) console.log(err);
else console.log(result_arr);
});
/**
* output:
* [4, 10]
*/
等待兩個function都正確執行結束後,呼叫final_callback,各個function的結果會依function在array中的順序填入result_arr裡頭。
arrar的方式如果你不喜歡,你也可以放入object。
async.series({
div_1: function() {
var callback = typeof arguments[arguments.length - 1] === "function" ? arguments[arguments.length - 1] : function() {};
div(16, 4, callback);
},
div_2: function() {
var callback = typeof arguments[arguments.length - 1] === "function" ? arguments[arguments.length - 1] : function() {};
div(100, 10, callback);
}
}, function(err, result_arr) {
if (err) console.log(err);
else console.log(result_arr);
});
/**
* output:
* { "div_1": 4, "div_2": 10 }
*/
回傳的結果會依function名稱輸出。
串列執行(async.parallel)
async.parallel([function array/object], final_callback);
將funtion array裡面的function逐一執行,上一個function結束(呼叫了async所給予的callback參數)後,就會執行下一個function。同series函數,只要其中一個發生error,就會直接呼叫final_callback。
這個函數可以有效的將非同步函數變成同步進行的感覺,但又不會浪費資源時間一直在做等待,而是等到確實執行完後才去接著做下一件事情。
async.parallel([
function() {
var callback = typeof arguments[arguments.length - 1] === "function" ? arguments[arguments.length - 1] : function() {};
div(16, 4, callback);
},
function() {
var callback = typeof arguments[arguments.length - 1] === "function" ? arguments[arguments.length - 1] : function() {};
div(100, 10, callback);
}
], function(err, result_arr) {
if (err) console.log(err);
else console.log(result_arr);
});
/**
* output:
* [4, 10]
*/
和series一樣,也可以將輸入的function array改為function object。
瀑布執行(async.waterfall)
async.waterfall([function array], final_callback);
waterfall就如同自面上意思,他是一層一層的向下執行,感覺上有點像是parallel,不一樣的是上一個function可以將callback結果傳遞給下一個執行的function。
async.waterfall([
function() {
var callback = typeof arguments[arguments.length - 1] === "function" ? arguments[arguments.length - 1] : function() {};
div(16, 4, callback); // callback(undefined, 4);
},
function(last_function_result) {
var callback = typeof arguments[arguments.length - 1] === "function" ? arguments[arguments.length - 1] : function() {};
div(100, last_function_result, callback);
}
], function(err, final_result) {
if (err) console.log(err);
else console.log(final_result);
});
/**
* output:
* 25
*/
和其他async的函數一樣,只要發生錯誤就會直接跳到final_callback,所以如果裡面的function是正確執行,而callback的第一個參數為undefined/null時,會直接將第二個以後的參數丟到下一個function裡頭,你callback裡面放n個,他就傳n.slice(1)個(排除第一個是error),就算只是傳undefined也會占用參數位置。
而final_callback結果的值,會是function array的最後一個function所傳入的值。
async.waterfall([
function() {
var callback = typeof arguments[arguments.length - 1] === "function" ? arguments[arguments.length - 1] : function() {};
callback(undefined, 1, 2);
},
function(one, two) {
var callback = typeof arguments[arguments.length - 1] === "function" ? arguments[arguments.length - 1] : function() {};
callback(undefined, 3);
},
function(three) {
var callback = typeof arguments[arguments.length - 1] === "function" ? arguments[arguments.length - 1] : function() {};
callback(undefined, 4);
}
], function(err, final_result) {
if (err) console.log(err);
else console.log(final_result);
});
/**
* output:
* 4
*/
async.each(array, iterator, final_callback);
用法和underscore的each或是array.eachOf很像,不一樣的是他是非同步執行的,可能你會覺得既然效果一樣為什麼要特別用非同步的方式處理呢?假設今天的array.length大於幾萬的時候,跑著each的那個function就會占用了大部分的運算時間,導致整支程式卡卡的,但若是用非同步處理就會和其他function一起排隊處理。
var arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
async.each(arr, function(value) {
var callback = typeof arguments[arguments.length - 1] === "function" ? arguments[arguments.length - 1] : function() {};
console.log(value);
callback();
}, function(err) {
if (err) console.log(err);
console.log("done");
});
/**
* output:
* 0
* 1
* 2
* 3
* 4
* 5
* 6
* 7
* 8
* 9
* 10
* done
*/
iterator的function model中,第一個所接收的參數是陣列中的值,但不像Array.eachOf會給予index和array本身。最後一個參數一樣是callback,每次執行完記得要呼叫callback告知該function以處理結束。同其他async的函數,有一個發生錯誤就直接執行final_callback。
each函數是並列執行的,但官方同樣也提供串列執行(async.eachSeries),還有限制同時可以執行幾個的方法。
補充:
if(err) console.log(err);
在async裡面,將err放進if裡面可以最簡單的判斷這個非同步function是否出現錯誤,不管原始套件開發者是填null或是undefined,都會被判定成false,而不執行該行程式碼。
註: 未定義變數 (undefined, null, define)
沒有留言:
張貼留言