Nodejs try catch捕捉異常失效場景

艾倫先生發表於2017-12-14

先看一個和非同步無關的錯誤處理

var test = undefined;
try{
    var f1 = function(){
         console.log(test.toString());  
    }
}
catch(e){
    console.log('error..');
}

//TypeError: Cannot read property 'toString' of undefined
//assume somewhere f1() will be called as an call back function
f1();
複製程式碼

try catch中的程式碼僅僅是宣告一個變數並且賦值,除非沒有足夠的記憶體,否則基本上不會丟擲異常,不管f1中的函式有沒有錯誤。f1函式的呼叫根本就沒有包含在try catch中,所以拋棄出未捕獲的異常那是100%的事情(至於賦給它的值所指向的函式有沒有錯誤顯然不在它的職責範圍之內,畢竟函式在這裡沒有執行。),這個和非同步毫無關係。

在看看跟非同步相關的

node.js是非同步IO執行,所以我們將try/catch放置非同步回撥函式中,當出現一個異常時,try/catch操作只能捕獲當次事件迴圈內的異常,我們通過try 拿到這個錯誤時錯過了當前程式執行堆疊。(或者理解成,非同步錯誤發生時在try catch塊結束時候,所以當然不會被catch)

之後 Node 會觸發 uncaughtException事件,而在node.js原生的uncaughtException 處理事件是掛在 process 物件上,所以,如果一個異常出現時,當前執行的 process 會直接掛掉,導致錯誤永遠不會走到 catch語句.

var test = undefined;

try{
    
    setTimeout(function(){
    	//TypeError: Cannot read property 'toString' of undefined
         console.log(test.toString());  
    }, 3000)
}
catch(e){
    console.log('error..');
}
複製程式碼

比如,在實際專案中,

var deserialize = require('deserialize'); 
// 假設 deserialize 是一個帶有 bug 的第三方模組

// app 是一個 express 服務物件
app.get('/users', function (req, res) {
    mysql.query('SELECT * FROM user WHERE id=1', function (err, user) {
        var config = deserialize(user.config); 
        // 假如這裡觸發了 deserialize 的 bug
        res.send(config);
    });
});
複製程式碼

如果不幸觸發了 deserialize 模組的 bug,這裡就會丟擲一個異常,最終結果是整個服務 crash。

當這種情況發生在 Web 服務上時結果是災難性的。uncaughtException 錯誤會導致當前的所有的使用者連線都被中斷,甚至不能返回一個正常的 HTTP 錯誤碼,使用者只能等到瀏覽器超時才能看到一個no data received錯誤。

這是一種非常野蠻粗暴的異常處理機制,一個友好的錯誤處理機制應該滿足三個條件:

  • 對於引發異常的使用者,返回 500 頁面
  • 其他使用者不受影響,可以正常訪問
  • 不影響整個程式的正常執行

相關文章