创建异常
本课程介绍如何处理异常。
抛出异常
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}