为什么需要自定义错误类型
JavaScript
原生提供了7种错误类型,分别是 Error
, EvalError
, SyntaxError
, RangeError
, ReferenceError
, TypeError
, 和 URIError
。当我们在编写提供给其他开发者使用的库(包)时,为了在必要的时候给予开发者错误信息反馈(例如,开发者所传参数类型不正确),我们通常会抛出错误,但是原生提供的这些错误类型不够有针对性,不能让开发者一眼就能看出是我们的库所抛出的错误,所以我们需要定制自己的错误类型,当开发者错误使用库并且运行代码时能够在控制台看到我们所定义的错误类型,例如:
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
基于原型的继承方式,两者各有利弊。