JS 中的 Error 對象. 包含了錯誤的具體信息,包括 name、message、錯誤堆棧 stack 等。能夠以 new Error 方式建立實例拋出,或調用 Error.captureStackTrace 爲已有對象添加 stack 錯誤堆棧信息 然後拋出
javascript
- Throw
Javascript 拋出的異常,是以 throw 方法拋出,未必都是 Error 的實例,但經過 nodeJs 或者 js 運行時發生的錯誤,都是 Error 的實例- EventEmitter
Nodejs 形式的錯誤回調,大部分流 & 異步事件都衍生自 EventEmitter 類 || 實例,如 fs, process, stream 等- Process
程序運行過程當中拋出的異常,或由底層庫拋出,或是運行中發生的一些 SyntaxError 之類
try .. catchhtml
經常使用的一種捕獲錯誤方式,瀏覽器 || node 環境均適用java
缺點:只針對同步異常有效node
try { throw new Error('Something went wrong here') } catch (err) { console.log(err) }
EventEmitter api
由 Events 模塊提供的 EventEmitter 類,基於 Observer 模式作的 publish/subscribe,經過 .on('error', ...) || .addEventlistener('error', ...) 註冊 subscriber,.emit() 發佈事件,但會有最大的 maxListener 的限制,可更改。
不 show 源碼了,特別簡單,本身去 look 一下。如 koa 的 app 就是基於 EventEmitter 的擴展,所以能夠經過監聽 errorpromise
class Koa extends EventEmitter {...} let app = new Koa() app.emit('error', ..) app.on('error', ...)
Process 瀏覽器
Process 進程對象也是 EventEmitter 的實例,可經過以下兩事件監聽 errorapp
- unhandledRejection
promise 的回調報錯,可經過監聽該事件 catch,但要注意因爲 promise 的 rejection catched 不知道會在啥時候才發生,因此實際上可能在 unhandledRejection 事件觸發後才 catch 了這個信息,對應有 rejectionHandled 事件監聽,以下:
process.on('rejectionHandled', p => { console.log('It has been handled') }) process.on('unhandledRejection', (reason, p) => { console.log('Went here') }) let test = new Promise((resolve, reject) => { let err = new Error('Just for a test') err.name = 'TestError' reject(err) }) setTimeout(() => { test.catch(err => { console.log('Excample work!') console.log(err) }) }, 10000) // 打印出來的信息順序是: // Went here // It has been handled // Excample work! // { TestError: Just for a test // ....(stack message here) }
(承上)
uncaughtException
其他 js 運行中發生 || 拋出的未捕獲錯誤,都可經過監聽該事件解決,若不進行該事件的監聽,發生異常時,會直接致使程序 crash
但不建議用這種方式 catch,程序運行中的錯誤 更應該是拋出來,全部的 error 都 catch 的話,豈不是程序均可以當作無 bug 了
but 在打錯誤日誌的時候是須要 catch 上報日誌的,可是在上報完後,須要繼續把 error throw,對於 uncaughtException callback 中拋出的異常不會再捕獲,而是以非 0 的狀態碼 exitdom
// Promise Rejection process.on('unhandledRejection', err => { process.nextTick(() => { throw err })) }) // 終極 boss process.on('uncaughtException', err => {...}) };
以上以一張圖爲總結:
koa
補充以前沒補充完的內容,下圖爲 node 中對於 process 的初始化等系列流程
- 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 類實例,fatal_exception_string 也是其中定義的,返回值爲 '_fatalException');
如下爲 FatalException 函數的部份內容
void FatalException(Isolate* isolate, Local<Value> error, Local<Message> message) { ...(省略) Local<String> fatal_exception_string = env->fatal_exception_string(); // '_fatalException' Local<Function> fatal_exception_function = process_object->Get(fatal_exception_string).As<Function>(); ...(省略) if (exit_code == 0) { TryCatch fatal_try_catch(isolate); // 調用 v8::TryCatch // Do not call FatalException when _fatalException handler throws fatal_try_catch.SetVerbose(false); // 關鍵點 // this will return true if the JS layer handled it, false otherwise Local<Value> caught = fatal_exception_function->Call(process_object, 1, &error); // 運行 process._fatalException 函數 if (fatal_try_catch.HasCaught()) { // 捕獲錯誤 // the fatal exception function threw, so we must exit ReportException(env, fatal_try_catch); exit_code = 7; } if (exit_code == 0 && false == caught->BooleanValue()) { ReportException(env, error, message); exit_code = 1; } } ...(省略) }
- 在調用 _fatalException 函數前,先調用 v8::TryCatch::setVerbose 把 verbose 設置爲 false,則運行時拋出的異常就不會再觸發 FatalException ;在捕獲到運行錯誤後,把 exit_code 設爲 7,並返回;這就解釋了 爲何在 uncaughtException 時拋出的異常不會再從新觸發回調,要知道 EventEmitter 可沒幫你作這樣的事情
- _fatalException 在觸發 "uncaughtException" 事件前實際上是會優先檢查 domain 是否存在,當 domain 不存在時纔會調用 uncaughtException 的,但 domain 這個 api 已經被廢除了,也就不累述了
- unhandledRejection 事件的觸發總體思路也差很少,首先會調用 SetupPromises 初始化,而後調用 v8::Isolate::SetPromiseRejectCallback 進行監聽 ... 而且跟 uncaughtException 不一樣的是 unhandledRejection 的觸發只會打印 warning 並不會把整個程序給 crash 了
若本文有誤,歡迎隨時指正