在 Node.js 中,提供了[error](https://nodejs.org/dist/latest-v18.x/docs/api/errors.html)模塊,并且內置了標準的 JavaScript 錯誤,常見的有:
* EvalError:在調用 eval() 函數時出現問題時拋出該錯誤。
* SyntaxError:調用不符合 JavaScript 的語法時拋出該錯誤。
* RangeError:超出可接受值的集合或范圍,例如數組越界。
* ReferenceError:訪問未定義的變量時拋出該錯誤。
* TypeError:參數或變量的類型有問題時拋出該錯誤。
* URIError:使用全局的 URI 處理函數發生問題時拋出該錯誤。
  本系列所有的示例源碼都已上傳至Github,[點擊此處](https://github.com/pwstrick/node)獲取。?
## 一、Error 類
  Node.js 生成的上述錯誤,都是 Error 類的實例或繼承自 Error 類。注意,運行時拋出的所有異常都將是 Error 的實例。
  Error 實例能捕獲堆棧跟蹤,并提供錯誤的文本描述。
  下面是一個簡單的示例,其中 message 屬性提供了錯誤的字符串描述,toString() 會生成文本消息。
  stack 屬性提供了完整的錯誤信息,包括錯誤描述和一系列堆棧幀(每行以 "at " 開頭),每一幀都描述了代碼中生成錯誤的調用點。
~~~
const e = new Error('test error');
// test error
console.log(e.message);
// Error: test error
console.log(e.toString());
// Error: test error
// at Object.<anonymous> (/Users/code/web/node/08/error.js:1:11)
// at Module._compile (node:internal/modules/cjs/loader:1108:14)
// at Object.Module._extensions..js (node:internal/modules/cjs/loader:1137:10)
// at Module.load (node:internal/modules/cjs/loader:988:32)
// at Function.Module._load (node:internal/modules/cjs/loader:828:14)
// at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:76:12)
// at node:internal/main/run_main_module:17:47
console.log(e.stack);
~~~
## 二、捕獲錯誤
  一些異常在 JavaScript 層是不可恢復的,會導致 Node.js 進程崩潰。
  所以有些異常需要被捕獲,在 Node.js 中有 3 種常用的捕獲方法:
* 錯誤優先的回調。
* throw 語句或 try-catch 語句。
* error 事件機制。
**1)錯誤優先的回調**
  Node.js 核心模塊暴露的大多數異步方法都遵循錯誤優先回調的慣用模式。
  使用這種模式,回調函數作為參數傳給方法,當操作完成或出現錯誤時,回調函數將使用 Error 實例作為第一個參數傳入。
  如果沒有出現錯誤,則第一個參數將作為 null 傳入。在下面的示例中,當讀取一個不存在的文件時,將拋出錯誤。
~~~
const fs = require('fs');
function errorCallback(err, data) {
// [Error: ENOENT: no such file or directory, open './data.txt'] {
// errno: -2,
// code: 'ENOENT',
// syscall: 'open',
// path: './data.txt'
// }
console.log(err);
}
fs.readFile('./data.txt', errorCallback);
~~~
**2)throw**
  throw 關鍵字后面可以跟任何類型的 JavaScript 值(字符串、數字或對象等)。
  不過在 Node.js 中,throw 不會拋出字符串,而僅拋出 Error 實例。
  直接拋出 Error 實例,和拋出其他類型的值,前者會顯示堆棧幀,而后者不會,如下所示。
~~~
// /Users/code/web/node/08/throw.js:2
// throw new Error('test error');
// ^
// Error: test error
// at test (/Users/code/web/node/08/throw.js:2:9)
// at Object.<anonymous> (/Users/code/web/node/08/throw.js:7:1)
// at Module._compile (node:internal/modules/cjs/loader:1108:14)
// at Object.Module._extensions..js (node:internal/modules/cjs/loader:1137:10)
// at Module.load (node:internal/modules/cjs/loader:988:32)
// at Function.Module._load (node:internal/modules/cjs/loader:828:14)
// at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:76:12)
// at node:internal/main/run_main_module:17:47
throw new Error('test error');
// /Users/code/web/node/08/throw.js:2
// throw 'test error';
// ^
// test error
// (Use `node --trace-uncaught ...` to show where the exception was thrown)
throw 'test error';
~~~
**3)try-catch**
  try-catch 語句不僅能捕獲同步代碼,還能捕獲異步的 async/await 發生的錯誤,如下所示,調用一個不存在的 func() 函數。
~~~
// 同步代碼
function try1() {
try{
func();
}catch(e) {
console.log(e);
console.log('try-catch end');
}
}
// async/await
async function test() {
func();
}
async function try2() {
try{
await test();
}catch(e) {
console.log(e);
console.log('async try-catch end');
}
}
~~~
  有一點需要注意,try-catch 無法捕獲異步的回調函數,例如定時器、process.nextTick() 中的回調。
~~~
try {
process.nextTick(function () {
func();
});
} catch (e) {
console.log('nextTick end');
}
~~~
  catch 分支中的打印并不會執行,因為當回調被調用時,周圍的代碼(包括 try-catch)都已經運行好退出了。
**4)error 事件機制**
  如果在程序執行過程中出現了未捕獲的異常,那么程序就會崩潰,Node.js 提供了幾個事件來兜底這類未捕獲的異常。
  首先是 process 上的[uncaughtException](https://nodejs.org/dist/latest-v18.x/docs/api/process.html#event-uncaughtexception)事件,當未捕獲的 JavaScript 異常冒泡到事件循環時,就會自動觸發該事件。
  就比如上面那個無法捕獲的 try-catch 問題,注冊了 uncaughtException 事件就能成功捕獲,如下所示。
~~~
// ReferenceError: func is not defined
// at /Users/code/web/node/08/uncaughtException.js:7:5
// at processTicksAndRejections (node:internal/process/task_queues:78:11)
process.on('uncaughtException', (err) => {
console.log(err);
});
~~~
  在捕獲后,就不會讓程序奔潰,后續代碼也能被順利運行。
  注意,uncaughtException 事件是用于異常處理的粗略機制,僅用作最后的兜底手段,歸根結底,那些異常不能無視還是需要修復的。
  使用 Promise 進行編程時,異常被封裝為“被拒絕的 promise”,有兩種捕獲方式。第一種是使用 promise.catch() 捕獲和處理,并通過 Promise 鏈傳播。
  第二種是注冊 process 的[unhandledRejection](https://nodejs.org/dist/latest-v18.x/docs/api/process.html#event-unhandledrejection)事件,當 Promise 被拒絕并且在事件循環的一個輪詢內沒有錯誤捕獲時,就會觸發此事件。
  unhandledRejection 事件回調程序包含兩個參數,第一個是任意類型的 Promise 被拒絕的理由,第二個是被拒絕的 Promise 對象。
~~~
process.on('unhandledRejection', (reason, promise) => {
console.log(reason);
console.log(promise);
});
~~~
  下面是兩種觸發方式,第一種是在 then() 回調中書寫錯誤代碼,第二種是綁定 reject() 方法。
~~~
// 第一種觸發方式
Promise.resolve().then((res) => {
return JSON.pasre(res); // 注意錯別字 pasre
});
// 第二種觸發方式
Promise.reject(new Error('資源尚未加載'));
~~~
  unhandledRejection 事件對于檢測和跟蹤尚未處理的被拒絕的 Promise 很有用。
**5)verror**
  在下面的示例中,會在異步回調中通過 throw 拋出一個錯誤。
~~~
function test() {
throw new Error('test error');
}
function main() {
setImmediate(() => test());
}
main();
~~~
  注意觀察下面的堆棧信息,僅僅標注了 test() 函數中出錯的那條語句的位置,但是再往上的 main() 并沒有被標注。
~~~
/Users/code/web/node/08/verror.js:2
throw new Error('test error');
^
Error: test error
at test (/Users/code/web/node/08/verror.js:2:9)
at Immediate.<anonymous> (/Users/code/web/node/08/verror.js:5:22)
at processImmediate (node:internal/timers:464:21)
~~~
  當函數的調用深度比較深時,一旦出錯,那么追溯程序完整的執行過程就比較困難。
  目前市面上有一款[verror](https://github.com/TritonDataCenter/node-verror)庫,可以將 Error 實例層層封裝,在每一層附加錯誤信息,最后通過 VError 實例就能獲取調試所需的信息,便于問題的定位。
~~~
const VError = require('verror');
function test(err) {
const err3 = new VError(err, 'test()');
console.log(err3.message); // test(): main(): test error
console.log(err3);
}
function main() {
setImmediate(() => {
const err1 = new Error('test error');
const err2 = new VError(err1, 'main()');
test(err2);
});
}
main();
~~~
  在上面的示例中,先實例化一個 Error 類,然后實例化一個 VError 類,構造函數的第二個參數就是提供給調試用的關鍵信息。
  將 VError 實例作為參數傳遞給 test() 函數,再實例化一個 VError 類,這其實就是層層包裝的過程。
  最后讀取 message 屬性,得到的值是 test(): main(): test error,這些就是附加的數據,以及錯誤描述。
  如果直接打印 VError 實例,那么能得到更多關鍵信息,包括行數,文件路徑等。
~~~
VError: test(): main(): test error
at test (/Users/code/web/node/08/verror.js:3:16)
at Immediate._onImmediate (/Users/code/web/node/08/verror.js:11:5)
at processImmediate (node:internal/timers:464:21) {
jse_shortmsg: 'test()',
jse_cause: VError: main(): test error
at Immediate._onImmediate (/Users/code/web/node/08/verror.js:10:18)
at processImmediate (node:internal/timers:464:21) {
jse_shortmsg: 'main()',
jse_cause: Error: test error
at Immediate._onImmediate (/Users/code/web/node/08/verror.js:9:18)
at processImmediate (node:internal/timers:464:21),
jse_info: {}
},
jse_info: {}
}
~~~
參考資料:
[捕獲異常](https://www.nodejs.red/#/nodejs/advanced/uncaugh-exception)?[診斷報告](https://www.nodejs.red/#/nodejs/modules/report)?
[餓了么調試面試題](https://github.com/ElemeFE/node-interview/tree/master/sections/zh-cn#%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86%E8%B0%83%E8%AF%95)
[\[譯\] NodeJS 錯誤處理最佳實踐](https://cnodejs.org/topic/55714dfac4e7fbea6e9a2e5d)
[異常處理與domain](https://yjhjstz.gitbooks.io/deep-into-node/content/chapter13/chapter13-2.html)
*****
> 原文出處:
[博客園-Node.js精進](https://www.cnblogs.com/strick/category/2154090.html)
[知乎專欄-前端性能精進](https://www.zhihu.com/column/c_1611672656142725120)
已建立一個微信前端交流群,如要進群,請先加微信號freedom20180706或掃描下面的二維碼,請求中需注明“看云加群”,在通過請求后就會把你拉進來。還搜集整理了一套[面試資料](https://github.com/pwstrick/daily),歡迎瀏覽。

推薦一款前端監控腳本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不僅能監控前端的錯誤、通信、打印等行為,還能計算各類性能參數,包括 FMP、LCP、FP 等。
- ES6
- 1、let和const
- 2、擴展運算符和剩余參數
- 3、解構
- 4、模板字面量
- 5、對象字面量的擴展
- 6、Symbol
- 7、代碼模塊化
- 8、數字
- 9、字符串
- 10、正則表達式
- 11、對象
- 12、數組
- 13、類型化數組
- 14、函數
- 15、箭頭函數和尾調用優化
- 16、Set
- 17、Map
- 18、迭代器
- 19、生成器
- 20、類
- 21、類的繼承
- 22、Promise
- 23、Promise的靜態方法和應用
- 24、代理和反射
- HTML
- 1、SVG
- 2、WebRTC基礎實踐
- 3、WebRTC視頻通話
- 4、Web音視頻基礎
- CSS進階
- 1、CSS基礎拾遺
- 2、偽類和偽元素
- 3、CSS屬性拾遺
- 4、浮動形狀
- 5、漸變
- 6、濾鏡
- 7、合成
- 8、裁剪和遮罩
- 9、網格布局
- 10、CSS方法論
- 11、管理后臺響應式改造
- React
- 1、函數式編程
- 2、JSX
- 3、組件
- 4、生命周期
- 5、React和DOM
- 6、事件
- 7、表單
- 8、樣式
- 9、組件通信
- 10、高階組件
- 11、Redux基礎
- 12、Redux中間件
- 13、React Router
- 14、測試框架
- 15、React Hooks
- 16、React源碼分析
- 利器
- 1、npm
- 2、Babel
- 3、webpack基礎
- 4、webpack進階
- 5、Git
- 6、Fiddler
- 7、自制腳手架
- 8、VSCode插件研發
- 9、WebView中的頁面調試方法
- Vue.js
- 1、數據綁定
- 2、指令
- 3、樣式和表單
- 4、組件
- 5、組件通信
- 6、內容分發
- 7、渲染函數和JSX
- 8、Vue Router
- 9、Vuex
- TypeScript
- 1、數據類型
- 2、接口
- 3、類
- 4、泛型
- 5、類型兼容性
- 6、高級類型
- 7、命名空間
- 8、裝飾器
- Node.js
- 1、Buffer、流和EventEmitter
- 2、文件系統和網絡
- 3、命令行工具
- 4、自建前端監控系統
- 5、定時任務的調試
- 6、自制短鏈系統
- 7、定時任務的進化史
- 8、通用接口
- 9、微前端實踐
- 10、接口日志查詢
- 11、E2E測試
- 12、BFF
- 13、MySQL歸檔
- 14、壓力測試
- 15、活動規則引擎
- 16、活動配置化
- 17、UmiJS版本升級
- 18、半吊子的可視化搭建系統
- 19、KOA源碼分析(上)
- 20、KOA源碼分析(下)
- 21、花10分鐘入門Node.js
- 22、Node環境升級日志
- 23、Worker threads
- 24、低代碼
- 25、Web自動化測試
- 26、接口攔截和頁面回放實驗
- 27、接口管理
- 28、Cypress自動化測試實踐
- 29、基于Electron的開播助手
- Node.js精進
- 1、模塊化
- 2、異步編程
- 3、流
- 4、事件觸發器
- 5、HTTP
- 6、文件
- 7、日志
- 8、錯誤處理
- 9、性能監控(上)
- 10、性能監控(下)
- 11、Socket.IO
- 12、ElasticSearch
- 監控系統
- 1、SDK
- 2、存儲和分析
- 3、性能監控
- 4、內存泄漏
- 5、小程序
- 6、較長的白屏時間
- 7、頁面奔潰
- 8、shin-monitor源碼分析
- 前端性能精進
- 1、優化方法論之測量
- 2、優化方法論之分析
- 3、瀏覽器之圖像
- 4、瀏覽器之呈現
- 5、瀏覽器之JavaScript
- 6、網絡
- 7、構建
- 前端體驗優化
- 1、概述
- 2、基建
- 3、后端
- 4、數據
- 5、后臺
- Web優化
- 1、CSS優化
- 2、JavaScript優化
- 3、圖像和網絡
- 4、用戶體驗和工具
- 5、網站優化
- 6、優化閉環實踐
- 數據結構與算法
- 1、鏈表
- 2、棧、隊列、散列表和位運算
- 3、二叉樹
- 4、二分查找
- 5、回溯算法
- 6、貪心算法
- 7、分治算法
- 8、動態規劃
- 程序員之路
- 大學
- 2011年
- 2012年
- 2013年
- 2014年
- 項目反思
- 前端基礎學習分享
- 2015年
- 再一次項目反思
- 然并卵
- PC網站CSS分享
- 2016年
- 制造自己的榫卯
- PrimusUI
- 2017年
- 工匠精神
- 2018年
- 2019年
- 前端學習之路分享
- 2020年
- 2021年
- 2022年
- 2023年
- 2024年
- 日志
- 2020