Nodejs exception handling

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

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

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


先看以下的 code

{% include_code blog-ianwu-tw-example-master/20130715_error_handling/syncCall_1.js %}

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

執行一下,果然出了 exception

{% codeblock lang:bash %} 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. (/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 {% endcodeblock %}

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


接著看下一個範例

{% include_code blog-ianwu-tw-example-master/20130715_error_handling/syncCall_2.js %}

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

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

{% codeblock lang:bash %} onlinemad:~ > ~/20130715_error_handling > node syncCall_2.js catch sycn exception {% endcodeblock %}

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


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

先看下面這段 code

{% include_code blog-ianwu-tw-example-master/20130715_error_handling/asyncCall_1.js %}

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

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

{% codeblock lang:bash %} 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

{% endcodeblock %}

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


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

{% include_code blog-ianwu-tw-example-master/20130715_error_handling/asyncCall_2.js %}

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

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

{% codeblock lang:bash %} onlinemad:~/20130715_error_handling > node asyncCall_2.js catch asycn exception {% endcodeblock %}


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

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

所以回傳的字串呢?

再合併 asyncCall_2.js 這個範例

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

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

{% include_code blog-ianwu-tw-example-master/20130715_error_handling/asyncCall_3.js %}

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

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

{% codeblock lang:bash %} onlinemad:~/20130715_error_handling > node asyncCall_3.js catch asycn exception onlinemad:~/20130715_error_handling > node asyncCall_3.js asyncCall {% endcodeblock %}


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

asyncCall_3.js 的範例修改如下

{% include_code blog-ianwu-tw-example-master/20130715_error_handling/asyncCall_catch_by_event.js %}

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

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

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

{% codeblock lang:bash %} onlinemad:~/20130715_error_handling > node asyncCall_catch_by_event.js [Error: async exception] onlinemad:~/20130715_error_handling > node asyncCall_catch_by_event.js asyncCall {% endcodeblock %}


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

這個範例是使用 domain 來 handle exception

{% include_code blog-ianwu-tw-example-master/20130715_error_handling/asyncCall_catch_by_domain.js %}

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

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

{% codeblock lang:bash %} onlinemad:~/20130715_error_handling > node asyncCall_catch_by_domain.js catch sync exception catch async exception {% endcodeblock %}

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

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


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

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

{% include_code blog-ianwu-tw-example-master/20130715_error_handling/asyncCall_catch_by_uncaughtException.js %}

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

{% codeblock lang:bash %} onlinemad:~/20130715_error_handling > node asyncCall_catch_by_uncaughtException.js catch async exception {% endcodeblock %}

不過不建議 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 (opens new window) 有興趣的朋友可以 clone 一份回家 😃