贊助商連結

Nodejs exception handling

例外處理(Error handling or exception handling) 永遠都是程式設計師的惡夢,我曾是(現在也是 XD)一個 java 程式設計師, java 內的 try catch 也是挺惱人的,即便 java 7 裡面有新的 muti try catch ,但是還是挺麻煩的。

轉到 nodejs 之後,發現 javascript 的 error handling 也不遑多讓,因為 nodejs 充斥了大量的 async ,所以處理起來格外辛苦,不過基本上來說,還是有一些準則可以來參考。

接著,讓我們話說重頭,從 sync code 開始來探討


先看以下的 code

syncCall_1.jsview raw
1
2
3
4
5
6
function syncCall(){
throw new Error('sync exception');
return 'syncCall';
}
syncCall();

這隻是典型的 sync call ,我們呼叫了 syncCall() 預期回傳一個字串,但是這個 function 可能會發生 error。

執行一下,果然出了 exception

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
onlinemad:~ > ~/20130715_error_handling > node syncCall_1.js
/20130715_error_handling/syncCall_1.js:2
throw new Error('sync exception');
^
Error: sync exception
at syncCall (/20130715_error_handling/syncCall_1.js:2:9)
at Object.<anonymous> (/20130715_error_handling/syncCall_1.js:6:1)
at Module._compile (module.js:456:26)
at Object.Module._extensions..js (module.js:474:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Function.Module.runMain (module.js:497:10)
at startup (node.js:119:16)
at node.js:901:3

設計這個 function 的人採用了 throw 把 error 丟出去給呼叫 syncCall() 的人處理,所以當使用 syncCall() 的時候必須注意 exception 的處理。


接著看下一個範例

syncCall_2.jsview raw
1
2
3
4
5
6
7
8
9
10
function syncCall(){
throw new Error('sync exception');
return 'syncCall';
}
try{
syncCall();
} catch(error){
console.log('catch sycn exception');
}

在 sync 的狀況下,用 try catch 就可以把 error 給抓住,所以 code 增加 try catch 來捕捉 exception

執行一下,果不其然 exception 被抓住並印出

1
2
onlinemad:~ > ~/20130715_error_handling > node syncCall_2.js
catch sycn exception

以上是 sync code 對於 exception 的處理方式。


不過正如一開始所說的 nodejs 充斥了大量的 async call 所以在處理 async call 的 exception 方式就跟 sync call 有很大的不同

先看下面這段 code

asyncCall_1.jsview raw
1
2
3
4
5
6
7
8
9
10
11
12
function asyncCall(cb){
process.nextTick(function(){
throw new Error('async exception');
cb('ayncCall');
});
}
try{
asyncCall();
} catch(error){
console.log('catch asycn exception');
}

這隻是典型的 async call ,我們呼叫了 asyncCall() 傳入一個 callback function ,asyncCall() 做完後會把字串透過 callback function 傳回來,但是這個 async function 也可能會發生 error。

於使我們用 try catch 來捕捉 exception 看看

1
2
3
4
5
6
7
8
9
10
11
12
onlinemad:~/20130715_error_handling > node asyncCall_1.js
/20130715_error_handling/asyncCall_1.js:3
throw new Error('async exception');
^
Error: async exception
at /20130715_error_handling/asyncCall_1.js:3:9
at process._tickCallback (node.js:415:13)
at Function.Module.runMain (module.js:499:11)
at startup (node.js:119:16)
at node.js:901:3

很遺憾,在 async 的狀況下,無法使用 try catch 的方式來捕捉 exception。


所以在 async 的狀況下我們就要將 exception 透過 callback function 傳給 caller 來處理。

asyncCall_2.jsview raw
1
2
3
4
5
6
7
8
9
10
11
function asyncCall(cb){
process.nextTick(function(){
cb(new Error('async exception'));
});
}
asyncCall(function(err){
if(err){
console.log('catch asycn exception');
}
});

接著,我們修改 code ,把 exception 傳回給 caller,所以 callback function 的第一個參數就是 exception,在 caller 這邊的就要檢查 err 的狀態,如果 not null 就表示出 exception 了。

執行一下,果然可以抓住 async exception

1
2
onlinemad:~/20130715_error_handling > node asyncCall_2.js
catch asycn exception


回想 asyncCall_1.js 這個範例 asyncCall()

做完後會把字串透過 callback function 傳回來

所以回傳的字串呢?

再合併 asyncCall_2.js 這個範例

把 exception 傳回給 caller,所以 callback function 的第一個參數就是 exception

所以 async call exception handling 最佳作法如下

asyncCall_3.jsview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function asyncCall(cb){
process.nextTick(function(){
if(Math.floor((Math.random()*10)%2)){
cb(new Error('async exception'));
} else {
cb(null, 'asyncCall');
}
});
}
asyncCall(function(err, str){
if(err){
console.log('catch asycn exception');
} else {
console.log(str);
}
});

當有 exception 發生時把 exception 放在 callback function 的第一個參數傳回給 caller,當一切正常時 callback function 的第一個參數就擺 null 第二個參數擺執行的結果,這樣一來就可以完美地處理 async call 所發生的 exception 。

執行一下,當沒有 exception 發生時,就會把字串傳回給 callback function;當發生 exception 時就可以抓住 exception。

1
2
3
4
onlinemad:~/20130715_error_handling > node asyncCall_3.js
catch asycn exception
onlinemad:~/20130715_error_handling > node asyncCall_3.js
asyncCall


除了傳 error 給 callback function 的方式之外,還可以用 EventEmitter trigger 特定的事件。

asyncCall_3.js 的範例修改如下

asyncCall_catch_by_event.jsview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var events = require('events');
var Async = function(){
events.EventEmitter.call(this);
}; require('util').inherits(Async, events.EventEmitter);
Async.prototype.asyncCall = function(){
var self = this;
process.nextTick(function(){
if(Math.floor((Math.random()*10)%2)){
self.emit('error', new Error('async exception'));
} else {
self.emit('success', 'asyncCall');
}
});
};
var async = new Async();
async.on('error', function(err){
console.log(err);
});
async.on('success', function(str){
console.log(str);
});
async.asyncCall();

當有 exception 發生時 emit 一個 error 事件,接著再把 exception 傳回去;當一切正常時 emit 一個 success 事件,同時把字串傳回去。

接著在使用這個 class 的時候就必須使用 on 綁一個 function 上去,這樣 emit 的事件才有對應的 function 可以處理。

執行一下,基本上跟 asyncCall_3.js 的範例一樣。

1
2
3
4
onlinemad:~/20130715_error_handling > node asyncCall_catch_by_event.js
[Error: async exception]
onlinemad:~/20130715_error_handling > node asyncCall_catch_by_event.js
asyncCall


另外還有兩種方式可以 handle exception,分別是使用 domain 跟 catch uncaughtException

這個範例是使用 domain 來 handle exception

asyncCall_catch_by_domain.jsview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var d1 = require('domain').create();
var d2 = require('domain').create();
function syncCall(){
throw new Error('sync exception');
return 'syncCall';
}
function asyncCall(cb){
process.nextTick(function(){
throw new Error('async exception');
cb('ayncCall');
});
}
d1.on('error', function(err){
console.log('catch', err.message);
});
d2.on('error', function(err){
console.log('catch', err.message);
});
d1.run(function(){
asyncCall(function(str){
console.log(str);
});
});
d2.run(function(){
syncCall();
});

d.run 把可能會出 exception 的 function 包起來,所以

執行一下,不論是 sync 或是 async call 都可以被 domain 的 on error function 抓住並處理

1
2
3
onlinemad:~/20130715_error_handling > node asyncCall_catch_by_domain.js
catch sync exception
catch async exception

不過 domain 的機制並不是再取代 try catch 也不是去抓住 async 的 exception,之後會在針對 domain 這個 module 來作探討。

更多細節可以參考 Node.js 的 API: Domain Node.js v0.10.15 Manual & Documentation


最後一種方式就是 catch uncaughtException 在 process module 可以綁一個 uncaughtException 事件

這個範例是使用 uncaughtException 事件來 handle exception

asyncCall_catch_by_uncaughtException.jsview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
process.on('uncaughtException', function(err) {
console.log('catch', err.message);
});
function asyncCall(cb){
process.nextTick(function(){
throw new Error('async exception');
cb('ayncCall');
});
}
asyncCall(function(str){
console.log(str);
})

執行一下,錯誤可以被 uncaughtException 的事件抓住並處理

1
2
onlinemad:~/20130715_error_handling > node asyncCall_catch_by_uncaughtException.js
catch async exception

不過不建議 uncaughtException 來處理 exception,因為官方有說

Note that uncaughtException is a very crude mechanism for exception handling and may be removed in the future.

所以寫出來只是讓大家看看而已...


語言能做的就是提供好的方式去抓 exception,但是抓到 exception 後要怎樣處理那就是 programmer 要決定的事情,是要停止執行還是繼續執行替代 function 這就不在語言的範圍了。

總結 Node.js 的 exception handling 以 syncCall_2.jsasyncCall_3.js 的方式去 handle exception 會是最好的方式。


ref


所有的範例都放在 github githut:onlinemad/blog-ianwu-tw-example 有興趣的朋友可以 clone 一份回家 :)