NodeJS異常處理uncaughtException篇

OneAPM1發表於2015-05-08





王龑 — APRIL 08, 2015


很多 NodeJS 的開發者在抱怨異常處理太麻煩,我們會透過一些列部落格梳理一下NodeJS中常見的異常處理的手段。
和大多數程式語言一樣,在 NodeJS 裡可以透過`throw`丟擲一個異常:
```
throw new Error('Catch me');
```
為了捕獲這個異常需要把程式碼包在`Try Catch`中:
```
try{
    throw new Error('Catch me');
}catch(e){
    // error captured
}
```
然而,由於 NodeJS 的非同步特性,上述程式碼只需稍加改造就會失效:
```
try{
    process.nextTick(function my_app(){
        throw new Error('Catch me');
    })
}catch(e){
    // never called
}
```
在現實世界裡,異常總是會產生在某個模組中。所謂模組就是能完成一個功能的單元,即使是一個簡單的函式也可以被看做一個模組。隨著專案程式碼行數增多,非同步巢狀的複雜性加強,經常會有異常沒捕獲的情況發生。一個沒有很強健壯性的 NodeJS 應用,會因為一個未捕獲的異常就整個掛掉,導致服務不可用。要改變大家覺得NodeJS是脆弱的這個認識,需要開發者加深對這門語言異常處理機制的瞭解。


##uncaughtException


`uncaughtException` 其實是 NodeJS 程式的一個事件。如果程式裡產生了一個異常而沒有被任何`Try Catch`捕獲會觸發這個事件。為了簡化問題,我們還是先看看同步情況下的例子。
```
function external() {
    throw new Error('Catch me');
}


function internal() {
    external();
}


internal(); //error will be thrown
```
在命令列裡執行這個程式,指令碼會在丟擲異常的那一行中斷。接下來,由於沒有`Try Catch`,異常會一直冒泡直到事件迴圈為止,而NodeJS對異常的預設處理非常簡單,處理的程式碼 *類似* 於:
```
function _MyFatalException(err){
    if(!process.emit('uncaughtException',err)){
        console.error(err.stack);
        process.emit('exit',1);
    }
}
```
NodeJS對於未捕獲異常的預設處理是: - 觸發 `uncaughtException` 事件 - 如果 uncaughtException 沒有被監聽,那麼 - 列印異常的堆疊資訊 - 觸發程式的 exit 事件


如果你正在用 NodeJS 開發伺服器,那麼你肯定不希望偶然的一個異常讓整個伺服器掛掉。那麼是不是隻要監聽了 `uncaughtException` 就可以阻止伺服器的程式退出呢? 答案是可以,**但是不要這麼做!**。看這個例子:
```
var express = require('express');


function external(cb) {
    process.nextTick(function () {
        throw new Error();
        cb.call(null, 'sunny');
    })
}


var app = express();
app.get('/weather', function (req, res) {
    external(function (data) {
        res.end('Weather of Beijing is ' + data);
    })
})
app.listen(8018);




function noop(){}
process.on('uncaughtException', noop)
```
上面這個例子假設使用者訪問站點的時候可以看到當地的天氣,我們用 `apache2-utils` 來模擬請求


> ab -n 1000 -c 20


糟糕!請求一直在等待,記憶體上漲。原因在於`res.end` 永遠不會執行,現有的`I/O`處於等待的狀態,已經開闢的資源不僅不會被釋放,而且伺服器還在不知疲倦地接受新的使用者請求。


在 NodeJS 中處理異常是代價高昂的,而且一不小心就會導致記憶體洩露和讓應用程式處於不穩定的狀態。為了提高健壯性,我們可以用`Cluster`模式,由之而來的推薦做法是: - 針對發生異常的請求返回一個錯誤程式碼 - 出錯的Worker不再接受新的請求 - 退出關閉Worker程式
***


 
**本文作者系[OneAPM]()工程師王龑 ,想閱讀更多好的[技術文章](),請訪問OneAPM官方技術部落格。**

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/30238867/viewspace-1630454/,如需轉載,請註明出處,否則將追究法律責任。