Logo
Section 1 / 4

创建异常

本课程介绍如何处理异常。

导入

创建异常

本课程介绍如何处理异常。

抛出异常

TypeScript 在异常处理方面遵循 ECMAScript 的规范。你可以使用 throw 关键字后跟一个期望的异常实例来抛出新的异常。通常建议抛出对象而非字符串,原因在于抛出的对象会携带完整的调用栈信息,因为该对象必须继承自基础接口 Error。Error 接口定义了名称(name)、消息(message)和堆栈跟踪(stack)。唯一不需要抛出异常实例的情况是使用 ErrorConstructor,它允许你直接通过 throw Error("可选的消息") 抛出错误。

1function throw1() { 2 throw "error in string"; 3} 4 5function throw2() { 6 throw Error("Message Here"); 7} 8 9function throw3() { 10 const err: Error = new Error("Message Here"); 11 throw err; 12} 13 14// throw1(); 15// throw2(); 16// throw3();

上述代码中有三行被注释。第一行 第14行 返回的是一个 string。当抛出时,输出如下:

/usercode/index.ts:3 throw "error in string";

再次注释掉 第14行,尝试 第15行 和 第16行。两者都会返回更丰富的输出:

/usercode/index.ts:6 throw Error("Message Here"); ^ Error: Message Here at throw2 (/usercode/index.ts:6:11) at Object. (/usercode/index.ts:15:1) at Module._compile (internal/modules/cjs/loader.js:778:30) at Module.m._compile (/usr/lib/node_modules/ts-node/src/index.ts:473:23) at Module._extensions..js (internal/modules/cjs/loader.js:789:10) at Object.require.extensions.(anonymous function) [as .ts] (/usr/lib/node_modules/ts-node/src/index.ts:476:12) at Module.load (internal/modules/cjs/loader.js:653:32) at tryModuleLoad (internal/modules/cjs/loader.js:593:12) at Function.Module._load (internal/modules/cjs/loader.js:585:3) at Function.Module.runMain (internal/modules/cjs/loader.js:831:12)

虽然输出内容略显复杂,但使用 Error 对象能提供更多信息。

自定义类型化错误

TypeScript 允许你继承 Error 类来创建自定义错误。自定义错误非常重要,因为它可以帮助你在后续处理异常时区分具体是哪种错误,从而进行相应的处理。虽然可以比较错误消息,但最佳实践是依赖更具唯一性的标识。自定义错误不仅支持比较异常名称,还能附加额外信息,如错误编号、上下文数据等。

📜 注意:以下代码会抛出错误 ❌

1class MySpecificError extends Error { 2 public data: string; 3 constructor(data: string, message: string) { 4 super(message); 5 Object.setPrototypeOf(this, MySpecificError.prototype); // 恢复原型链 6 // 或者: 7 // Object.setPrototypeOf(this, new.target.prototype); // 恢复原型链 8 this.data = data; 9 } 10} 11 12throw new MySpecificError("dataHere", "My Message");

上述代码需要调用 Object.setPrototypeOf,你可能会问为什么。原因是自 TypeScript 2.1 版本起,在使用 ES6 语法时,Error 构造函数使用 new.target 来处理原型链。在之前的 ECMAScript 版本中并不存在这种机制。通过手动调用或使用类的原型进行赋值,我们确保原型链正确地引用了 new.target。如果忘记这一步,将会导致在后续使用 instanceof 判断异常类型时出现问题,而这是我们在接下来的课程中将要使用的技巧。

不使用 Error 的异常

最佳实践是始终使用 Error() 或 new Error(),它们是等价的解决方案,而不是直接抛出字符串。这样可以获得更丰富的错误信息,包括堆栈跟踪。

更好的写法是:

1throw Error("Message");

而不是:

1throw "Message";

保持谨慎

如同其他语言一样,异常仅用于处理非正常流程中的特殊情况。原因是异常流的逻辑更难理解。

存在一些替代方案:

例如,出错的函数可以返回一个错误对象或 undefined。然而,这种方式的问题在于它占用了原本可能返回的有效值的位置。可以通过返回目标类型并与异常类型联合来缓解这一问题。

确实,联合类型方案会在每次调用时引入更多的类型检查,这可能不够便捷。

1function withReturn(p1: number): number | Error { 2 if (wrong) { 3 return new Error("My error message"); 4 } 5 return 42; // 期望的返回值 6}

另一种传递异常的方式是使用回调函数参数,在发生错误时调用该函数。

1function withCallback( 2 p1: number, 3 error: (errMsg: string) => void 4): string { 5 if (wrong) { 6 error("My error message"); 7 } 8 return "String as expected when all good"; 9}

最后,在使用 Promise 的场景中,可以选择拒绝(reject)该 Promise。

1function withPromise(p1: number): Promise<string> { 2 if (wrong) { 3 return Promise.reject("My error message"); 4 } 5 return Promise.resolve("String as expected when all good"); 6}

高级变量

捕获同步异常