Node 錯誤處理之挖坑系列

EventEmitter:Nodejs 形式的錯誤回調,大部分流 & 異步事件都衍生自 EventEmitter 類 || 實例,如 fs, process, stream 等。

Process:程序運行過程中拋出的異常,或由底層庫拋出,或是運行中發生的一些 SyntaxError 之類。

三、錯誤捕獲幾種方式

try .. catch:常用的一種捕獲錯誤方式,瀏覽器 || node 環境均適用。

缺點:只針對同步異常有效。

Node 錯誤處理之挖坑系列

EventEmitter

由 Events 模塊提供的 EventEmitter 類,基於 Observer 模式做的 publish/subscribe,通過 .on('error', ...)|| .addEventlistener('error', ...) 註冊 subscriber,.emit() 發佈事件,但會有最大的 maxListener 的限制,可更改。     

不 show 源碼了,特別簡單,自己去 look 一下。如 koa 的 app 就是基於 EventEmitter 的擴展,因此可以通過監聽 error。

Node 錯誤處理之挖坑系列

Process

Process 進程對象也是 EventEmitter 的實例,可通過如下兩事件監聽 error。

unhandledRejection :promise 的回調報錯,可通過監聽該事件 catch,但要注意由於 promise 的 rejection catched 不知道會在啥時候才發生,所以實際上可能在 unhandledRejection 事件觸發後才 catch 了這個信息,對應有 rejectionHandled 事件監聽,如下:

Node 錯誤處理之挖坑系列

(承上) uncaughtException

:其餘 js 運行中發生 || 拋出的未捕獲錯誤,均可通過監聽該事件解決,若不進行該事件的監聽,發生異常時,會直接導致程序 crash。但不建議用這種方式 catch,程序運行中的錯誤 更應該是拋出來,所有的 error 都 catch 的話,豈不是程序都可以看成無 bug 了。but 在打錯誤日誌的時候是需要 catch 上報日誌的,但是在上報完後,需要繼續把 error throw,對於 uncaughtException callback 中拋出的異常不會再捕獲,而是以非 0 的狀態碼 exit。

Node 錯誤處理之挖坑系列

四、小結

說了這麼多,就做個小小總結吧 以上關係網可以概括為如圖:

Node 錯誤處理之挖坑系列

個人見解,實際處理中基於幾點準則:

1、對於需要具象化的錯誤信息,也就是我們需要知道具體是哪一塊的錯誤,並且在錯誤發生時即進行個性化處理。這一類型的錯誤,在程序中執行時要對可能會出錯的函數進行 catch,方式有多種: try ... catch(同步); 或 通過 node 形式的標準錯誤回調 (err) => {...}(要求所調用的方法,支持該種寫法); 或 監聽 error 事件 .on('error', err => {...})(要求所調用的對象繼承自 EventEmitter 實例,並內部發生錯誤時,會 emit('error'))。

2、promise 形式的錯誤,可在 promise 實例的末尾再引入 .catch,可捕獲該實例所有 then 中拋出的異常;另外 async 的引入也允許了我們可以如同步操作一樣捕獲錯誤。

3、但有時候程序的運行,不可能針對所有的函數操作等都去 try ... catch,也不能保證程序運行一定就無 bug,但必須要能夠把這些異常都捕獲並上報。因此 process 的兩個大 boss: unhandledRejection & uncaughtException,是必須引入的,在程序最外層引入,且越靠前越好。此外針對 unhandledRejection,為防止說把不必要的 rejection 上報,可以在收到事件時,先把被 reject 的 promise 和 error 存儲起來,設置時間 maxTimeout ,超過 maxTimeout 仍未收到 rejectionHandled 的 promise 事件,再上報。

koa 項目為例:

Node 錯誤處理之挖坑系列

五、源碼解讀

下圖為 node 中對於 process 的初始化等系列流程:

Node 錯誤處理之挖坑系列

node.cc 其實是 node 運行主要的文件,其中定義了三個重載函數 Start,調用順序為 3 → 2 → 1,每個函數參數不同處理不同的邏輯;

isolate->AddMessageListener 的監聽事件 OnMessage 會在 js 運行發生錯誤時觸發,嗯,是的,只有 error 才會傳遞 ;這很好的解釋了為什麼 process 監聽 uncaughtException 就可以監聽到所有的拋出的非 promise 異常;

OnMessage 調用了 FatalException 函數,FatalException 引用 process.fatalException 並傳入 error (env 是 env.h 中聲明的 Environment 類實例,fatalexceptionstring 也是其中定義的,返回值為 'fatalException'); 以下為 FatalException 函數的部分內容。

Node 錯誤處理之挖坑系列

在調用 fatalException 函數前,先調用 v8::TryCatch::setVerbose 把 verbose 設置為 false,則運行時拋出的異常就不會再觸發 FatalException ;在捕獲到運行錯誤後,把 exitcode 設為 7,並返回;這就解釋了 為什麼在 uncaughtException 時拋出的異常不會再重新觸發回調,要知道 EventEmitter 可沒幫你做這樣的事情。

_fatalException 在觸發 "uncaughtException" 事件前其實是會優先檢查 domain 是否存在,當 domain 不存在時才會調用 uncaughtException 的,但 domain 這個 api 已經被廢除了,也就不累述了。

unhandledRejection 事件的觸發整體思路也差不多,首先會調用 SetupPromises 初始化,然後調用 v8::Isolate::SetPromiseRejectCallback 進行監聽 ... 並且跟 uncaughtException 不同的是 unhandledRejection 的觸發只會打印 warning 並不會把整個程序給 crash 了。

六、END

參考網站:https://v8.paulfryzel.com/docs/master/index.html


分享到:


相關文章: