问题历史:在 JavaScript 中,标准错误通过 Error 类来描述。在大型的 TypeScript 项目中,使用自己的错误层次结构以准确处理场景是很重要的,但在类型、继承和 'instanceof' 行为上有一些细微之处。
问题:从 Error 继承时可能会遇到困难:原型会丢失,'instanceof' 不会正常工作,类型可能描述不正确,序列化常常导致堆栈跟踪丢失。如果没有明确指定属性,在处理错误时可能会出现 Bug。
解决方案:通过扩展 Error 类来正确地定义自定义错误。应明确写出名称,手动恢复原型(对于 ES5 和编译到 CommonJS 时很重要),并对错误字段进行类型化。
代码示例:
class ValidationError extends Error { code: number; constructor(message: string, code: number = 400) { super(message); Object.setPrototypeOf(this, ValidationError.prototype); this.name = 'ValidationError'; this.code = code; } } function process(user: string) { if (!user) throw new ValidationError('User required', 401); }
关键特性:
为什么不能省略调用 Object.setPrototypeOf(this, ...)?
如果不调用 Object.setPrototypeOf(this, Class.prototype),在编译为 ES5/CommonJS 或 Babel 时,'instanceof' ValidationError 将不会起作用。这将导致 catch ValidationError 块无法捕获错误。
class CustomErr extends Error {} const err = new CustomErr('msg'); console.log(err instanceof CustomErr); // false 没有 setPrototypeOf
可以省略自定义错误的 name 字段吗?
如果不设置 this.name 属性,错误的堆栈和日志显示将不正确,这将使查找原因和分类错误变得困难。
错误需要可序列化吗,如果需要,怎么做?
错误应正确序列化(例如,用于记录或通过网络传输),否则 JSON.stringify(new Error()) 不会返回消息和堆栈。应重写 toJSON 方法。
class SerializableError extends Error { toJSON() { return { name: this.name, message: this.message, stack: this.stack }; } }
在项目中简单地实现了 class MyError extends Error,没有恢复原型。通过 if (err instanceof MyError) 捕获错误,但这并没有起作用,代码 silent 地跳过了对关键情况的处理。
优点:
缺点:
实现了正确的 CustomError,明确了 name、code 和 toJSON,测试覆盖了不同类型错误的处理。在日志和捕获处理器中,错误结构变得清晰,因此减少了查找 Bug 的时间。
优点:
缺点: