编程后端开发者

描述 TypeScript 中自定义错误机制的工作原理。如何正确声明自定义错误类,为什么要指定它们的类型,以及如何避免 'instanceof' 和错误序列化的陷阱?

用 Hintsage AI 助手通过面试

答案。

问题历史:在 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); }

关键特性:

  • 明确恢复原型以支持 'instanceof',即使在转译时也是如此。
  • 自定义错误属性的类型化。
  • 每个逻辑错误组的单独类。

误导性问题。

为什么不能省略调用 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 }; } }

类型错误和反模式

  • 缺少 Object.setPrototypeOf 导致 instanceof 工作不正确
  • 忽略 name 和自定义属性
  • 在没有实现 toJSON 的情况下序列化错误:堆栈/消息丢失

生活实例

负面案例

在项目中简单地实现了 class MyError extends Error,没有恢复原型。通过 if (err instanceof MyError) 捕获错误,但这并没有起作用,代码 silent 地跳过了对关键情况的处理。

优点:

  • 代码看起来简洁
  • 没有样板代码

缺点:

  • instanceof 不工作
  • 异常没有正确记录
  • 在 ts->js 转译器切换时出现不兼容问题

正面案例

实现了正确的 CustomError,明确了 name、code 和 toJSON,测试覆盖了不同类型错误的处理。在日志和捕获处理器中,错误结构变得清晰,因此减少了查找 Bug 的时间。

优点:

  • 清晰的类型错误层次结构
  • 可靠的序列化
  • 在浏览器和 Node.js 之间的跨平台性

缺点:

  • 每个 CustomError 类中出现了模板逻辑。