ES5 / ES6 自定義錯誤型別比較

xvrzhao發表於2018-08-05

為什麼需要自定義錯誤型別

JavaScript 原生提供了7種錯誤型別,分別是 Error, EvalError, SyntaxError, RangeError, ReferenceError, TypeErrorURIError。當我們在編寫提供給其他開發者使用的庫(包)時,為了在必要的時候給予開發者錯誤資訊反饋(例如,開發者所傳引數型別不正確),我們通常會丟擲錯誤,但是原生提供的這些錯誤型別不夠有針對性,不能讓開發者一眼就能看出是我們的庫所丟擲的錯誤,所以我們需要定製自己的錯誤型別,當開發者錯誤使用庫並且執行程式碼時能夠在控制檯看到我們所定義的錯誤型別,例如:

CustomError: something bad happened.

而不是 :

Error: something bad happened.

另外,我們可以為自己的錯誤型別新增額外的資訊欄位,原生提供的錯誤只有 name(錯誤型別),message(錯誤資訊),stack(部分js環境提供堆疊追蹤資訊),這些欄位可能不能夠滿足我們的需求,我們想給我們所丟擲的錯誤新增一個 reason 欄位來表明丟擲錯誤的原因,那麼我們就需要自定義錯誤型別。

自定義錯誤需要繼承自Error

我們自定義的錯誤型別最好繼承自Error類,但這並不是強制規定,這只是一種良好的程式碼習慣,因為當開發者通過 try / catch 捕獲到異常時,可能會進行進一步的判斷:

if (error instanceof Error) { ... }

這樣我們的自定義錯誤才能被有效捕獲。

ES5 自定義錯誤的寫法

function CustomError(message) {
    // 例項化自定義錯誤時所傳的錯誤資訊引數
    this.message = message
    // name 指明該錯誤型別(同時在控制檯所列印的錯誤型別即由此欄位指明),不指明預設為Error。
    this.name = 'CustomError'
    // 捕獲到當前執行環境的堆疊追蹤資訊,為自定義錯誤例項新增 `stack` 欄位進行儲存,
    // 第二個引數的含義為:堆疊追蹤只會展示到`CustomError`這個函式(即自定義錯誤的建構函式)被呼叫之前。
    Error.captureStackTrace(this, CustomError) 
}

// 原型鏈繼承 (詳見 JS高程 一書)
CustomError.prototype = new Error 
// 在自定義錯誤的原型上新增構造器函式為CustomError,
// 若不新增構造器,當獲取自定義錯誤的構造器時,獲取的是上一步`new Error`例項的原型的構造器,即`Error`建構函式
CustomError.prototype.constructor = CustomError 
複製程式碼

ES6 自定義錯誤的寫法

ES6 新增了 class 這一語法糖,可以方便的寫出繼承關係:

class CustomError extends Error {
    constructor (message) {
        super(message)
        this.name = 'CustomError',
        // 這一步可不寫,預設會儲存堆疊追蹤資訊到自定義錯誤建構函式之前,
        // 而如果寫成 `Error.captureStackTrace(this)` 則自定義錯誤的建構函式也會被儲存到堆疊追蹤資訊
        Error.captureStackTrace(this, this.constructor)
    }
}
複製程式碼

測試

接下來我們通過ES 5/6 兩種不同的寫法來自定義錯誤型別後, 使用下面的程式碼在 Node.js 中進行測試。我們先推測堆疊追蹤自棧頂向下依次應該是:

at new CustomError // 若堆疊追蹤到自定義錯誤的建構函式時,會有此列印
at c
at b
at a
複製程式碼

測試程式碼:

// `c` 函式丟擲自定義錯誤
function c() {
    throw new CustomError('something bad happened!')
}

// `b` 函式呼叫 `c`
function b() {
    c()
}

// `a` 函式呼叫 `b`
function a() {
    b()
}

try {
    a() // 執行 `a` 函式
} catch (err) {
    // 判斷所丟擲的錯誤是否為 `Error` 的子類例項
    if (err instanceof Error) {
        console.log('Capture the error:')
        console.log(err.stack) // 列印堆疊追蹤資訊
        console.log(err instanceof CustomError, err instanceof Error) // 錯誤是否為(或繼承自)CustomError 和 Error 型別
        console.log(err.constructor.name) // 自定義錯誤的構造器名稱
    }
}
複製程式碼

對比ES 5/6 兩種自定義錯誤的方式,執行結果均為:

Capture the error:
CustomError: something bad happened!
    at c (/Users/xavier/Documents/demos/javascript-demo/test.js:19:8)
    at b (/Users/xavier/Documents/demos/javascript-demo/test.js:23:2)
    at a (/Users/xavier/Documents/demos/javascript-demo/test.js:27:2)
    at Object.<anonymous> (/Users/xavier/Documents/demos/javascript-demo/test.js:31:2)
    at Module._compile (internal/modules/cjs/loader.js:689:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
    at Module.load (internal/modules/cjs/loader.js:599:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
    at Function.Module._load (internal/modules/cjs/loader.js:530:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
true true
CustomError
複製程式碼

測試說明兩種自定義錯誤的方式效果相同,ES6 class 寫法雖然方便,但只是語法糖,ES5 的寫法有利於我們理解 JavaScript 基於原型的繼承方式,兩者各有利弊。

相關文章