七天學會NodeJS(三)行程管理(util.format、Process、Child Process、Cluster)、子行程父行程之間通訊、異步編程、例外處理、域(Domain)
文章目錄
- 七天學會NodeJS(三)行程管理(util.format、Process、Child Process、Cluster)、子行程父行程之間通訊、異步編程、例外處理、域(Domain)
- 1. 行程管理
- 開門紅
- API走馬觀花
- Process
- Child Process
- Cluster
- 應用場景
- 如何獲取命令列引數
- 如何退出程式
- 如何控制輸入輸出
- 如何降權
- 如何創建子行程
- 行程間如何通訊
- 如何守護子行程
- 小結
- 2. 異步編程
- 回呼
- 代碼設計模式
- 函式回傳值
- 遍歷陣列
- 例外處理
- 域(Domain)
- 陷阱
- 小結
總結:
行程管理
本章小結
- 使用
process物件管理自身,- 使用
child_process模塊創建和管理子行程,util.format(format, […])
- 根據第一個引數,回傳一個格式化字串,類似
printf的格式化輸出,- 第一個引數是一個字串,包含零個或多個占位符, 每一個占位符被替換為與其對應的轉換后的值, 支持的占位符有:
%s- 字串.%d- 數字 (整型和浮點型).%j- JSON. 如果這個引數包含回圈物件的參考,將會被替換成字串'[Circular]',%%- 單獨一個百分號('%'),不會消耗一個引數,- 如果占位符沒有相對應的引數,占位符將不會被替換,
Proces
官方檔案: http://nodejs.org/api/process.html
在NodeJS中,可以通過
process物件感知和控制NodeJS自身行程的方方面面,
process不是內置模塊,而是一個全域物件,因此在任何地方都可以直接使用,如何獲取命令列引數
- 在NodeJS中可以通過
process.argv獲取命令列引數- 由于
argv[0]固定等于NodeJS執行程式的絕對路徑,argv[1]固定等于主模塊的絕對路徑,- 第一個命令列引數從
argv[2]開始如何退出程式
- 正常退出狀態碼=0
- 捕捉例外后退出 process.exit(1)
如何控制輸入輸出
- NodeJS程式的標準輸入流(stdin)、一個標準輸出流(stdout)、一個標準錯誤流(stderr)分別對應
process.stdin、process.stdout和process.stderr,- 第一個是只讀資料流,后邊兩個是只寫資料流
子行程父行程之間通訊
/* parent.js 這是父行程*/ //父行程在創建子行程,在options.stdio欄位中通過ipc開啟了一條IPC通道,之后就可以監聽子行程物件的message事件接收來自子行程的訊息 var child = child_process.spawn('node', [ 'child.js' ], { stdio: [ 0, 1, 2, 'ipc' ] }); child.on('message', function (msg) { console.log(msg); }); //通過.send方法給子行程發送訊息 child.send({ hello: 'hello' }); /* child.js 這是子行程*/ //在子行程這邊,可以在process物件上監聽message事件接收來自父行程的訊息 process.on('message', function (msg) { msg.hello = msg.hello.toUpperCase(); //并通過.send方法向父行程發送訊息, process.send(msg); });Child Process
- 官方檔案: http://nodejs.org/api/child_process.html
- 使用
child_process模塊可以創建和控制子行程,- 該模塊提供的API中最核心的是
.spawn,其余API都是針對特定使用場景對它的進一步封裝,算是一種語法糖,
- 使用
.spawn(exec, args, options)方法,創建子行程,該方法支持三個引數,- 第一個引數是執行檔案路徑,可以是執行檔案的相對或絕對路徑,也可以是根據PATH環境變數能找到的執行檔案名,
- 第二個引數中,陣列中的每個成員都按順序對應一個命令列引數,
- 第三個引數可選,用于配置子行程的執行環境與行為,
Cluster
cluster模塊是對child_process模塊的進一步封裝,專用于解決單行程NodeJS Web服務器無法充分利用多核CPU的問題,- 使用該模塊可以簡化多行程服務器程式的開發,讓每個核上運行一個作業行程,并統一通過主行程監聽埠和分發請求,
異步編程
本章小結
- 不掌握異步編程就不算學會NodeJS,
- 異步編程依托于回呼來實作,而使用回呼不一定就是異步編程,
- 異步編程下的函式間資料傳遞、陣列遍歷和例外處理與同步編程有很大差別,
- 使用
domain模塊簡化異步代碼的例外處理,并小心陷阱,簡介
- NodeJS最大的賣點——事件機制和異步IO,對開發者并不是透明的,
回呼
你不知道用戶何時單擊按鈕, 因此,為點擊事件定義了一個事件處理程式, 該事件處理程式會接受一個函式,該函式會在該事件被觸發時被呼叫:
document.getElementById('button').addEventListener('click', () => { //被點擊 })這就是所謂的回呼,回呼是一個簡單的函式,會作為值被傳給另一個函式,并且僅在事件發生時才被執行,
我們仍然回到JS是單執行緒運行的這個事實上,這決定了JS在執行完一段代碼之前無法執行包括回呼函式在內的別的代碼,也就是說,即使平行執行緒完成作業了,通知JS主執行緒執行回呼函式了,回呼函式也要等到JS主執行緒空閑時才能開始執行,
例外處理
在NodeJS中,幾乎所有異步API都按照以下方式設計,回呼函式中第一個引數都是
err,因此我們在撰寫自己的異步函式時,也可以按照這種方式來處理例外,與NodeJS的設計風格保持一致,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: object is not a function回呼函式已經讓代碼變得復雜了,而異步方式下對例外的處理更加劇了代碼的復雜度,如果NodeJS的最大賣點最后變成這個樣子,那就沒人愿意用NodeJS了,因此接下來會介紹NodeJS提供的一些解決方案,
域(Domain)
官方檔案: http://nodejs.org/api/domain.html
可以簡化異步代碼的例外處理
為了讓代碼好看點,我們可以在每處理一個請求時,使用
domain模塊創建一個子域(JS子運行環境),在子域內運行的代碼可以隨意拋出例外,而這些例外可以通過子域物件的error事件統一捕獲,function async(request, callback) { // Do something. asyncA(request, function (data) { // Do something asyncB(request, function (data) { // Do something asyncC(request, function (data) { // Do something callback(data); }); }); }); } http.createServer(function (request, response) { var d = domain.create(); d.on('error', function () { response.writeHead(500); response.end(); }); d.run(function () { async(request, function (data) { response.writeHead(200); response.end(data); }); }); });
1. 行程管理
NodeJS可以感知和控制自身行程的運行環境和狀態,也可以創建子行程并與其協同作業,這使得NodeJS可以把多個程式組合在一起共同完成某項作業,并在其中充當膠水和調度器的作用,本章除了介紹與之相關的NodeJS內置模塊外,還會重點介紹典型的使用場景,
開門紅
我們已經知道了NodeJS自帶的fs模塊比較基礎,把一個目錄里的所有檔案和子目錄都拷貝到另一個目錄里需要寫不少代碼,另外我們也知道,終端下的cp命令比較好用,一條cp -r source/* target命令就能搞定目錄拷貝,那我們首先看看如何使用NodeJS呼叫終端命令來簡化目錄拷貝,示例代碼如下:
var child_process = require('child_process');
var util = require('util');
function copy(source, target, callback) {
child_process.exec(
util.format('cp -r %s/* %s', source, target), callback);
}
copy('a', 'b', function (err) {
// ...
});
從以上代碼中可以看到,子行程是異步運行的,通過回呼函式回傳執行結果,
API走馬觀花
我們先大致看看NodeJS提供了哪些和行程管理有關的API,這里并不逐一介紹每個API的使用方法,官方檔案已經做得很好了,
Process
官方檔案: http://nodejs.org/api/process.html
任何一個行程都有啟動行程時使用的命令列引數,有標準輸入標準輸出,有運行權限,有運行環境和運行狀態,在NodeJS中,可以通過process物件感知和控制NodeJS自身行程的方方面面,另外需要注意的是,process不是內置模塊,而是一個全域物件,因此在任何地方都可以直接使用,
Child Process
官方檔案: http://nodejs.org/api/child_process.html
使用child_process模塊可以創建和控制子行程,該模塊提供的API中最核心的是.spawn,其余API都是針對特定使用場景對它的進一步封裝,算是一種語法糖,
Cluster
官方檔案: http://nodejs.org/api/cluster.html
cluster模塊是對child_process模塊的進一步封裝,專用于解決單行程NodeJS Web服務器無法充分利用多核CPU的問題,使用該模塊可以簡化多行程服務器程式的開發,讓每個核上運行一個作業行程,并統一通過主行程監聽埠和分發請求,
應用場景
和行程管理相關的API單獨介紹起來比較枯燥,因此這里從一些典型的應用場景出發,分別介紹一些重要API的使用方法,
如何獲取命令列引數
在NodeJS中可以通過process.argv獲取命令列引數,但是比較意外的是,node執行程式路徑和主模塊檔案路徑固定占據了argv[0]和argv[1]兩個位置,而第一個命令列引數從argv[2]開始,為了讓argv使用起來更加自然,可以按照以下方式處理,
function main(argv) {
// ...
}
main(process.argv.slice(2));
如何退出程式
通常一個程式做完所有事情后就正常退出了,這時程式的退出狀態碼為0,或者一個程式運行時發生了例外后就掛了,這時程式的退出狀態碼不等于0,如果我們在代碼中捕獲了某個例外,但是覺得程式不應該繼續運行下去,需要立即退出,并且需要把退出狀態碼設定為指定數字,比如1,就可以按照以下方式:
try {
// ...
} catch (err) {
// ...
process.exit(1);
}
如何控制輸入輸出
NodeJS程式的標準輸入流(stdin)、一個標準輸出流(stdout)、一個標準錯誤流(stderr)分別對應process.stdin、process.stdout和process.stderr,第一個是只讀資料流,后邊兩個是只寫資料流,對它們的操作按照對資料流的操作方式即可,例如,console.log可以按照以下方式實作,
function log() {
process.stdout.write(
util.format.apply(util, arguments) + '\n');
}
如何降權
在Linux系統下,我們知道需要使用root權限才能監聽1024以下埠,但是一旦完成埠監聽后,繼續讓程式運行在root權限下存在安全隱患,因此最好能把權限降下來,以下是這樣一個例子,
http.createServer(callback).listen(80, function () {
var env = process.env,
uid = parseInt(env['SUDO_UID'] || process.getuid(), 10),
gid = parseInt(env['SUDO_GID'] || process.getgid(), 10);
process.setgid(gid);
process.setuid(uid);
});
上例中有幾點需要注意:
- 如果是通過
sudo獲取root權限的,運行程式的用戶的UID和GID保存在環境變數SUDO_UID和SUDO_GID里邊,如果是通過chmod +s方式獲取root權限的,運行程式的用戶的UID和GID可直接通過process.getuid和process.getgid方法獲取, process.setuid和process.setgid方法只接受number型別的引數,- 降權時必須先降GID再降UID,否則順序反過來的話就沒權限更改程式的GID了,
如何創建子行程
以下是一個創建NodeJS子行程的例子,
var child = child_process.spawn('node', [ 'xxx.js' ]);
child.stdout.on('data', function (data) {
console.log('stdout: ' + data);
});
child.stderr.on('data', function (data) {
console.log('stderr: ' + data);
});
child.on('close', function (code) {
console.log('child process exited with code ' + code);
});
上例中使用了.spawn(exec, args, options)方法,該方法支持三個引數,第一個引數是執行檔案路徑,可以是執行檔案的相對或絕對路徑,也可以是根據PATH環境變數能找到的執行檔案名,第二個引數中,陣列中的每個成員都按順序對應一個命令列引數,第三個引數可選,用于配置子行程的執行環境與行為,
另外,上例中雖然通過子行程物件的.stdout和.stderr訪問子行程的輸出,但通過options.stdio欄位的不同配置,可以將子行程的輸入輸出重定向到任何資料流上,或者讓子行程共享父行程的標準輸入輸出流,或者直接忽略子行程的輸入輸出,
行程間如何通訊
在Linux系統下,行程之間可以通過信號互相通信,以下是一個例子,
/* parent.js */
var child = child_process.spawn('node', [ 'child.js' ]);
child.kill('SIGTERM');
/* child.js */
process.on('SIGTERM', function () {
cleanUp();
process.exit(0);
});
在上例中,父行程通過.kill方法向子行程發送SIGTERM信號,子行程監聽process物件的SIGTERM事件回應信號,不要被.kill方法的名稱迷惑了,該方法本質上是用來給行程發送信號的,行程收到信號后具體要做啥,完全取決于信號的種類和行程自身的代碼,
另外,如果父子行程都是NodeJS行程,就可以通過IPC(行程間通訊)雙向傳遞資料,以下是一個例子,
/* parent.js 這是父行程*/
//父行程在創建子行程,在options.stdio欄位中通過ipc開啟了一條IPC通道,之后就可以監聽子行程物件的message事件接收來自子行程的訊息
var child = child_process.spawn('node', [ 'child.js' ], {
stdio: [ 0, 1, 2, 'ipc' ]
});
child.on('message', function (msg) {
console.log(msg);
});
//通過.send方法給子行程發送訊息
child.send({ hello: 'hello' });
/* child.js 這是子行程*/
//在子行程這邊,可以在process物件上監聽message事件接收來自父行程的訊息
process.on('message', function (msg) {
msg.hello = msg.hello.toUpperCase();
//并通過.send方法向父行程發送訊息,
process.send(msg);
});
可以看到,父行程在創建子行程時,在options.stdio欄位中通過ipc開啟了一條IPC通道,之后就可以監聽子行程物件的message事件接收來自子行程的訊息,并通過.send方法給子行程發送訊息,在子行程這邊,可以在process物件上監聽message事件接收來自父行程的訊息,并通過.send方法向父行程發送訊息,資料在傳遞程序中,會先在發送端使用JSON.stringify方法序列化,再在接收端使用JSON.parse方法反序列化,
如何守護子行程
守護行程一般用于監控作業行程的運行狀態,在作業行程不正常退出時重啟作業行程,保障作業行程不間斷運行,以下是一種實作方式,
/* daemon.js */
function spawn(mainModule) {
var worker = child_process.spawn('node', [ mainModule ]);
worker.on('exit', function (code) {
if (code !== 0) {
spawn(mainModule);
}
});
}
spawn('worker.js');
可以看到,作業行程非正常退出時,守護行程立即重啟作業行程,
小結
本章介紹了使用NodeJS管理行程時需要的API以及主要的應用場景,總結起來有以下幾點:
- 使用
process物件管理自身, - 使用
child_process模塊創建和管理子行程,
2. 異步編程
NodeJS最大的賣點——事件機制和異步IO,對開發者并不是透明的,開發者需要按異步方式撰寫代碼才用得上這個賣點,而這一點也遭到了一些NodeJS反對者的抨擊,但不管怎樣,異步編程確實是NodeJS最大的特點,沒有掌握異步編程就不能說是真正學會了NodeJS,本章將介紹與異步編程相關的各種知識,
回呼
在代碼中,異步編程的直接體現就是回呼,異步編程依托于回呼來實作,但不能說使用了回呼后程式就異步化了,我們首先可以看看以下代碼,
function heavyCompute(n, callback) {
var count = 0,
i, j;
for (i = n; i > 0; --i) {
for (j = n; j > 0; --j) {
count += 1;
}
}
callback(count);
}
heavyCompute(10000, function (count) {
console.log(count);
});
console.log('hello');
-- Console ------------------------------
100000000
hello
可以看到,以上代碼中的回呼函式仍然先于后續代碼執行,JS本身是單執行緒運行的,不可能在一段代碼還未結束運行時去運行別的代碼,因此也就不存在異步執行的概念,
但是,如果某個函式做的事情是創建一個別的執行緒或行程,并與JS主執行緒并行地做一些事情,并在事情做完后通知JS主執行緒,那情況又不一樣了,我們接著看看以下代碼,
setTimeout(function () {
console.log('world');
}, 1000);
console.log('hello');
-- Console ------------------------------
hello
world
這次可以看到,回呼函式后于后續代碼執行了,如同上邊所說,JS本身是單執行緒的,無法異步執行,因此我們可以認為setTimeout這類JS規范之外的由運行環境提供的特殊函式做的事情是創建一個平行執行緒后立即回傳,讓JS主行程可以接著執行后續代碼,并在收到平行行程的通知后再執行回呼函式,除了setTimeout、setInterval這些常見的,這類函式還包括NodeJS提供的諸如fs.readFile之類的異步API,
另外,我們仍然回到JS是單執行緒運行的這個事實上,這決定了JS在執行完一段代碼之前無法執行包括回呼函式在內的別的代碼,也就是說,即使平行執行緒完成作業了,通知JS主執行緒執行回呼函式了,回呼函式也要等到JS主執行緒空閑時才能開始執行,以下就是這么一個例子,
function heavyCompute(n) {
var count = 0,
i, j;
for (i = n; i > 0; --i) {
for (j = n; j > 0; --j) {
count += 1;
}
}
}
var t = new Date();
setTimeout(function () {
console.log(new Date() - t);
}, 1000);
heavyCompute(50000);
-- Console ------------------------------
8520
可以看到,本來應該在1秒后被呼叫的回呼函式因為JS主執行緒忙于運行其它代碼,實際執行時間被大幅延遲,
代碼設計模式
異步編程有很多特有的代碼設計模式,為了實作同樣的功能,使用同步方式和異步方式撰寫的代碼會有很大差異,以下分別介紹一些常見的模式,
函式回傳值
使用一個函式的輸出作為另一個函式的輸入是很常見的需求,在同步方式下一般按以下方式撰寫代碼:
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.
}));
可以看到,以上代碼在異步函式執行一次并回傳執行結果后才傳入下一個陣列成員并開始下一輪執行,直到所有陣列成員處理完畢后,通過回呼的方式觸發后續代碼的執行,
如果陣列成員可以并行處理,但后續代碼仍然需要所有陣列成員處理完畢后才能執行的話,則異步代碼會調整成以下形式:
(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.
}));
可以看到,與異步串行遍歷的版本相比,以上代碼并行處理所有陣列成員,并通過計數器變數來判斷什么時候所有陣列成員都處理完畢了,
例外處理
JS自身提供的例外捕獲和處理機制——try..catch..,只能用于同步執行的代碼,以下是一個例子,
function sync(fn) {
return fn();
}
try {
sync(null);
// Do something.
} catch (err) {
console.log('Error: %s', err.message);
}
-- Console ------------------------------
Error: object is not a function
可以看到,例外會沿著代碼執行路徑一直冒泡,直到遇到第一個try陳述句時被捕獲住,但由于異步函式會打斷代碼執行路徑,異步函式執行程序中以及執行之后產生的例外冒泡到執行路徑被打斷的位置時,如果一直沒有遇到try陳述句,就作為一個全域例外拋出,以下是一個例子,
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 ------------------------------
/home/user/test.js:4
callback(fn());
^
TypeError: object is not a function
at null._onTimeout (/home/user/test.js:4:13)
at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
因為代碼執行路徑被打斷了,我們就需要在例外冒泡到斷點之前用try陳述句把例外捕獲住,并通過回呼函式傳遞被捕獲的例外,于是我們可以像下邊這樣改造上邊的例子,
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: object is not a function
可以看到,例外再次被捕獲住了,在NodeJS中,幾乎所有異步API都按照以上方式設計,回呼函式中第一個引數都是err,因此我們在撰寫自己的異步函式時,也可以按照這種方式來處理例外,與NodeJS的設計風格保持一致,
有了例外處理方式后,我們接著可以想一想一般我們是怎么寫代碼的,基本上,我們的代碼都是做一些事情,然后呼叫一個函式,然后再做一些事情,然后再呼叫一個函式,如此回圈,如果我們寫的是同步代碼,只需要在代碼入口點寫一個try陳述句就能捕獲所有冒泡上來的例外,示例如下,
function main() {
// Do something.
syncA();
// Do something.
syncB();
// Do something.
syncC();
}
try {
main();
} catch (err) {
// Deal with exception.
}
但是,如果我們寫的是異步代碼,就只有呵呵了,由于每次異步函式呼叫都會打斷代碼執行路徑,只能通過回呼函式來傳遞例外,于是我們就需要在每個回呼函式里判斷是否有例外發生,于是只用三次異步函式呼叫,就會產生下邊這種代碼,
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的最大賣點最后變成這個樣子,那就沒人愿意用NodeJS了,因此接下來會介紹NodeJS提供的一些解決方案,
域(Domain)
官方檔案: http://nodejs.org/api/domain.html
NodeJS提供了domain模塊,可以簡化異步代碼的例外處理,在介紹該模塊之前,我們需要首先理解“域”的概念,簡單的講,一個域就是一個JS運行環境,在一個運行環境中,如果一個例外沒有被捕獲,將作為一個全域例外被拋出,NodeJS通過process物件提供了捕獲全域例外的方法,示例代碼如下
process.on('uncaughtException', function (err) {
console.log('Error: %s', err.message);
});
setTimeout(function (fn) {
fn();
});
-- Console ------------------------------
Error: undefined is not a function
雖然全域例外有個地方可以捕獲了,但是對于大多數例外,我們希望盡早捕獲,并根據結果決定代碼的執行路徑,我們用以下HTTP服務器代碼作為例子:
function async(request, callback) {
// Do something.
asyncA(request, function (err, data) {
if (err) {
callback(err);
} else {
// Do something
asyncB(request, function (err, data) {
if (err) {
callback(err);
} else {
// Do something
asyncC(request, function (err, data) {
if (err) {
callback(err);
} else {
// Do something
callback(null, data);
}
});
}
});
}
});
}
http.createServer(function (request, response) {
async(request, function (err, data) {
if (err) {
response.writeHead(500);
response.end();
} else {
response.writeHead(200);
response.end(data);
}
});
});
以上代碼將請求物件交給異步函式處理后,再根據處理結果回傳回應,這里采用了使用回呼函式傳遞例外的方案,因此async函式內部如果再多幾個異步函式呼叫的話,代碼就變成上邊這副鬼樣子了,為了讓代碼好看點,我們可以在每處理一個請求時,使用domain模塊創建一個子域(JS子運行環境),在子域內運行的代碼可以隨意拋出例外,而這些例外可以通過子域物件的error事件統一捕獲,于是以上代碼可以做如下改造:
function async(request, callback) {
// Do something.
asyncA(request, function (data) {
// Do something
asyncB(request, function (data) {
// Do something
asyncC(request, function (data) {
// Do something
callback(data);
});
});
});
}
http.createServer(function (request, response) {
var d = domain.create();
d.on('error', function () {
response.writeHead(500);
response.end();
});
d.run(function () {
async(request, function (data) {
response.writeHead(200);
response.end(data);
});
});
});
可以看到,我們使用.create方法創建了一個子域物件,并通過.run方法進入需要在子域中運行的代碼的入口點,而位于子域中的異步函式回呼函式由于不再需要捕獲例外,代碼一下子瘦身很多,
陷阱
無論是通過process物件的uncaughtException事件捕獲到全域例外,還是通過子域物件的error事件捕獲到了子域例外,在NodeJS官方檔案里都強烈建議處理完例外后立即重啟程式,而不是讓程式繼續運行,按照官方檔案的說法,發生例外后的程式處于一個不確定的運行狀態,如果不立即退出的話,程式可能會發生嚴重記憶體泄漏,也可能表現得很奇怪,
但這里需要澄清一些事實,JS本身的throw..try..catch例外處理機制并不會導致記憶體泄漏,也不會讓程式的執行結果出乎意料,但NodeJS并不是存粹的JS,NodeJS里大量的API內部是用C/C++實作的,因此NodeJS程式的運行程序中,代碼執行路徑穿梭于JS引擎內部和外部,而JS的例外拋出機制可能會打斷正常的代碼執行流程,導致C/C++部分的代碼表現例外,進而導致記憶體泄漏等問題,
因此,使用uncaughtException或domain捕獲例外,代碼執行路徑里涉及到了C/C++部分的代碼時,如果不能確定是否會導致記憶體泄漏等問題,最好在處理完例外后重啟程式比較妥當,而使用try陳述句捕獲例外時一般捕獲到的都是JS本身的例外,不用擔心上訴問題,
小結
本章介紹了JS異步編程相關的知識,總結起來有以下幾點:
- 不掌握異步編程就不算學會NodeJS,
- 異步編程依托于回呼來實作,而使用回呼不一定就是異步編程,
- 異步編程下的函式間資料傳遞、陣列遍歷和例外處理與同步編程有很大差別,
- 使用
domain模塊簡化異步代碼的例外處理,并小心陷阱,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/296610.html
標籤:其他
上一篇:(Framework7 移動webapp) Springboot 入門培訓 8 Component 模板MVVM與AJAX
下一篇:vuex的基本概念
