NodeJS最大的賣點--事件機制和異步IO,對開發者並不透明編程
var output = fn1(fn2('input')); // Do something;
在異步方式下,因爲函數執行結果不是經過返回值,而是經過回調函數傳遞,所以通常按如下方式編寫代碼:設計模式
fn2('input', function(output2) { fn1(output2, function(output1) { //Do something. }); });
var len = arr.length, i = 0; for(;i < len; ++i) { arr[i] = sync(arr[i]); } //All array items have processed.
如果異步執行,以上代碼就沒法保證循環結束後全部數組成員都處理完畢了,若是數組成員必須一個接一個串行處理,則按如下方式編寫代碼:數組
(function next(i, len, callback) { if(i < len) { async(arr[i], function(value) { arr[i] = value; next(i + 1, len, callback); }); } else { callback(); } }(0, arr.length, function() { // All array items have processed. }));
能夠看到,在異步函數執行一次並返回執行結果後才傳入下一個數組成員並開始下一輪執行,直到全部數組成員處理完畢後,經過回調的方式觸發後續代碼執行。服務器
若數組成員能夠並行處理,但後續代碼仍然須要全部數組成員處理完畢後才能執行的話,則異步代碼調整成如下形式:dom
(function(i, len, count, callback) { for(;i < len; ++i) { (function(i) { async(arr[i], function(value) { arr[i] = value; if(++count === len) { callback(); } }); }(i)); } }(0, arr.length, 0, function() { //All array items have processed. }));
以上代碼並行處理全部成員,並經過計數器變量來判斷何時全部數組成員都處理完畢了異步
function async(fn, callback) { // Code execution path breaks here. setTimeout(function() { callback(fn()); }, 0); } try { async(null, function(data) { // Do something. }); } catch(err) { console.log('Error: %s', err.message); } -----------------------Console---------------------------- E:\Language\Javascript\NodeJS\try_catch.js:25 callback(fn()); ^ TypeError: fn is not a function at null._onTimeout (E:\Language\Javascript\NodeJS\try_catch.js:25:18) at Timer.listOnTimeout (timers.js:92:15)
由於代碼執行路徑被打斷了,咱們就須要在異常冒泡到斷點以前用try語句把異常捕獲注,並經過回調函數傳遞被捕獲的異常,改造:async
function async(fn, callback) { // Code execution path breaks here. setTimeout(function() { try { callback(null, fn()); } catch(err) { callback(err); } }, 0); } async(null, function(err, data) { if(err) { console.log('Error: %s', err.message); } else { // Do something } }) ----------------------Console---------------------- Error: fn is not a function
function main(callback) { // Do something. asyncA(function(err, data) { if(err) { callback(err); } else { // Do something. asyncB(function(err, data) { if(err) { callback(err); } else { // Do something. asyncC(function(err, data) { if(err) { callback(err); } else { // Do something. callback(null); } }); } }); } }); } main(function(err) { if(err) { // Deal with exception. } });
回調函數已經讓代碼變得複雜了,而異步之下的異常處理更加重了代碼的複雜度,幸虧,NodeJS提供了一些解決方案。異步編程
process.on('uncaughtException', function(err) { console.log('Error: %s', err.message); }); setTimeout(function(fn) { fn(); }); ------------------------Console-------------------- Error: fn is not a function
function async(req, callback) { // Do something. asyncA(req, function(err, data) { if(err) { callback(err); } else { // Do something. asyncB(req, function(err, data) { if(err) { callback(err); } else { // Do something. asyncC(req, function(err, data) { if(err) { callback(err); } else { // Do something. callback(null, data); } }); } }); } }); } http.createServer(function(req, res) { async(req, function(err, data) { if(err) { res.writeHead(500); res.end(); } else { res.writeHead(200); res.end(); } }); });
以上將請求對象交給異步函數處理,再根據處理結果返回響應,這裏採用了使用回調函數傳遞異常的方案,所以async函數內部若再多幾個異步函數調用的話,代碼就更難看了,爲了讓代碼好看點,能夠在沒處理一個請求時,使用domain模塊建立一個子域,在子域內運行的代碼能夠隨意拋出異常,而這些異常能夠經過子域對象的error事件統一捕獲,改造:函數
function async(req, callback) { // Do something. asyncA(req, function(data) { // Do something. asyncB(req, function(data) { // Do something. asyncC(req, function(data) { // Do something. callback(data); }); }); }); } http.createServer(function(req, res) { var d = domain.create(); d.on('error', function() { res.writeHead(500); res.end(); }); d.run(function() { async(req, function(data) { res.writeHead(200); res.end(data); }); }); });
注:JS的throw...tyr...catch異常處理機制並不會致使內存泄漏和使程序執行出乎意料,而是由於NodeJS並非純粹的JS,NodeJS裏大量的API內部是用C/C++實現的,所以NodeJS程序運行過程當中,代碼執行路徑穿梭於JS引擎內外部,而JS異常拋出機制可能打斷正常代碼的執行流程,致使C/C++部分的代碼表現異常,進而致使內存泄漏。spa
所以,使用uncaughtException或domain捕獲異常,代碼執行路徑裏涉及到了C/C++部分的代碼時,若不能肯定是否會致使內存泄漏等問題,最好在處理完異常後重啓程序比較穩當,而使用try語句捕獲的異常通常是JS自己的異常,不用擔憂上述問題