TypeScript: TypeScript 错误处理
最后更新:2026-08-26
错误处理是任何项目都绕不开的话题——TypeScript 的类型系统让错误处理更安全、更可预测。
1. JavaScript 的错误处理基础
(1) try/catch/finally
TYPESCRIPT
try {
let data = JSON.parse(input);
console.log(data);
} catch (error) {
// error 类型是 unknown(strict 模式下)
console.log("解析失败");
} finally {
console.log("清理资源");
// 无论成功失败都会执行
}
(2) throw 语句
JavaScript 可以 throw 任何值——不仅是 Error 对象:
TYPESCRIPT
throw new Error("出错了"); // ✅ 标准做法
throw "出错了"; // ⚠️ 可以但不好
throw 404; // ⚠️ 可以但不好
throw { code: 500, msg: "服务器错误" }; // ⚠️ 可以但不好
📌 建议: 永远 throw Error 对象(或其子类)——它携带调用栈信息,便于调试。throw 字符串或数字无法获取调用栈。
(3) Error 类型
TYPESCRIPT
interface Error {
name: string; // 错误名称(如 "TypeError"、"RangeError")
message: string; // 错误描述
stack?: string; // 调用栈(非标准但广泛支持)
}
2. 内置错误类型与类型收窄
(1) 常见内置错误
| 错误类型 | 触发场景 |
|---|---|
Error |
通用错误 |
TypeError |
类型错误(如 null.toString()) |
RangeError |
值超出范围(如递归溢出) |
SyntaxError |
语法错误(如 JSON.parse 失败) |
ReferenceError |
引用未定义变量 |
URIError |
URI 编解码错误 |
(2) instanceof 收窄错误类型
TYPESCRIPT
function processValue(value: unknown) {
try {
let num = Number(value);
if (isNaN(num)) throw new TypeError("不是有效数字");
if (num < 0) throw new RangeError("不能为负数");
return num;
} catch (error) {
if (error instanceof TypeError) {
console.log(`类型错误:${error.message}`); // ✅ 收窄为 TypeError
} else if (error instanceof RangeError) {
console.log(`范围错误:${error.message}`); // ✅ 收窄为 RangeError
} else if (error instanceof Error) {
console.log(`其他错误:${error.message}`); // ✅ 收窄为 Error
} else {
// 理论上不会到达——JavaScript 总是 throw Error 或其子类
console.log("未知错误");
}
}
}
3. 自定义错误类
(1) 基本自定义错误
TYPESCRIPT
class AppError extends Error {
constructor(message: string) {
super(message);
this.name = "AppError"; // 设置错误名称
}
}
class ValidationError extends AppError {
constructor(
message: string,
public field: string // 额外的错误信息
) {
super(message);
this.name = "ValidationError";
}
}
class NotFoundError extends AppError {
constructor(
public resource: string,
public id: number | string
) {
super(`${resource} (${id}) 不存在`);
this.name = "NotFoundError";
}
}
(2) 使用自定义错误
TYPESCRIPT
function findUser(id: number) {
if (id <= 0) {
throw new ValidationError("ID必须为正数", "id");
}
// 模拟查找
if (id > 100) {
throw new NotFoundError("用户", id);
}
return { id, name: "用户" + id };
}
try {
let user = findUser(999);
} catch (error) {
if (error instanceof ValidationError) {
console.log(`验证失败:字段 ${error.field},${error.message}`);
} else if (error instanceof NotFoundError) {
console.log(`未找到:${error.resource},ID=${error.id}`);
} else if (error instanceof Error) {
console.log(`错误:${error.message}`);
}
}
▶ 示例:HTTP 错误体系
TYPESCRIPT
📖 仅展示
class HttpError extends Error {
constructor(
public statusCode: number,
message: string
) {
super(message);
this.name = "HttpError";
}
}
class BadRequestError extends HttpError {
constructor(message: string) {
super(400, message);
this.name = "BadRequestError";
}
}
class UnauthorizedError extends HttpError {
constructor(message: string = "未授权") {
super(401, message);
this.name = "UnauthorizedError";
}
}
class ForbiddenError extends HttpError {
constructor(message: string = "禁止访问") {
super(403, message);
this.name = "ForbiddenError";
}
}
class NotFoundError2 extends HttpError {
constructor(resource: string) {
super(404, `${resource} 不存在`);
this.name = "NotFoundError";
}
}
// 使用
function handleRequest(path: string) {
if (!path.startsWith("/api/")) {
throw new BadRequestError("路径必须以 /api/ 开头");
}
if (path === "/api/admin") {
throw new ForbiddenError();
}
return { data: "响应数据" };
}
try {
let result = handleRequest("/api/users");
console.log(result.data);
} catch (error) {
if (error instanceof HttpError) {
console.log(`HTTP ${error.statusCode}: ${error.message}`);
}
}
输出:
TEXT
📖 仅展示
响应数据
4. Result 模式
try/catch 的问题是"异常是隐式的"——函数签名不告诉你它可能抛异常。Result 模式让错误成为返回值的一部分:
(1) 定义 Result 类型
TYPESCRIPT
type Success<T> = { ok: true; value: T };
type Failure<E> = { ok: false; error: E };
type Result<T, E = Error> = Success<T> | Failure<E>;
(2) 创建 Result
TYPESCRIPT
function success<T>(value: T): Success<T> {
return { ok: true, value };
}
function failure<E>(error: E): Failure<E> {
return { ok: false, error };
}
(3) 使用 Result 替代 try/catch
TYPESCRIPT
function divide(a: number, b: number): Result<number, string> {
if (b === 0) {
return failure("除数不能为零");
}
return success(a / b);
}
let result1 = divide(10, 2);
let result2 = divide(10, 0);
if (result1.ok) {
console.log(result1.value); // 5 ✅
} else {
console.log(result1.error);
}
if (result2.ok) {
console.log(result2.value);
} else {
console.log(result2.error); // "除数不能为零" ✅
}
(4) Result 工具方法
TYPESCRIPT
function tryCatch<T>(fn: () => T): Result<T, Error> {
try {
return success(fn());
} catch (error) {
return failure(error instanceof Error ? error : new Error(String(error)));
}
}
async function tryAsync<T>(fn: () => Promise<T>): Promise<Result<T, Error>> {
try {
return success(await fn());
} catch (error) {
return failure(error instanceof Error ? error : new Error(String(error)));
}
}
// 使用
let parseResult = tryCatch(() => JSON.parse('{"name":"Charlie"}'));
if (parseResult.ok) {
console.log(parseResult.value.name); // "Charlie"
}
5. catch 中 error 类型的处理
(1) strict 模式下 error 是 unknown
TYPESCRIPT
try {
JSON.parse("invalid");
} catch (error) {
// error 类型是 unknown(TypeScript 4.4+)
// console.log(error.message); // ❌ unknown 没有message属性
// ✅ 方式一:instanceof 检查
if (error instanceof Error) {
console.log(error.message);
}
// ✅ 方式二:类型断言(谨慎)
let msg = (error as Error).message;
// ✅ 方式三:类型守卫函数
function getErrorMessage(error: unknown): string {
if (error instanceof Error) return error.message;
if (typeof error === "string") return error;
return "未知错误";
}
console.log(getErrorMessage(error));
}
(2) 旧版 TypeScript 的 error 类型
TYPESCRIPT
// TypeScript 4.3 及更早——catch 的 error 类型是 any
// 可以在 tsconfig 中用 useUnknownInCatchVariables: true 改为 unknown
// TypeScript 4.4+——默认 unknown(strict 模式下)
❓ 常见问题
Q try/catch 和 Result 模式该用哪个?
A 两种方式不互斥。try/catch 是 JavaScript/TypeScript 的标准异常处理——适合不可预测的运行时错误(网络故障、JSON 解析等)。Result 模式适合可预测的业务错误——函数签名明确告诉你"可能失败",调用者必须处理。项目内统一风格即可。
Q 为什么 catch 的 error 是 unknown 而不是 Error?
A 因为 JavaScript 允许 throw 任何值——
throw "消息" 或 throw 42 都是合法的。TypeScript 无法保证 catch 到的一定是 Error 实例,所以用 unknown 是最安全的。实际开发中,99% 的 throw 都是 Error 对象——用 instanceof Error 收窄即可。Q 自定义错误类需要调用 super 吗?
A 必须。自定义错误类继承 Error 时,构造函数中必须调用
super(message) 来初始化 Error 的 message 属性。同时建议手动设置 this.name——否则 name 默认是父类的名字。Q finally 中的代码会影响返回值吗?
A finally 中的 return 会覆盖 try/catch 中的 return——这是一个常见的 bug 来源。建议 finally 中只做资源清理(关闭文件、释放锁等),不要放 return 语句。
📖 小节
- JavaScript 内置 Error 体系——Error、TypeError、RangeError 等子类
- 用 instanceof 在 catch 中收窄错误类型,安全访问特定错误的属性
- 自定义错误类继承 Error,添加业务特定属性(field、statusCode 等)
- Result 模式让错误成为返回值的一部分——可辨识联合 ok/error
- catch 中 error 类型是 unknown——用 instanceof 或类型守卫安全处理
- 永远 throw Error 对象,不要 throw 字符串或数字
📝 作业
- 基础题(难度⭐):写一个函数
safeParse(json: string): Result<object, Error>,用 try/catch 包裹 JSON.parse,成功返回 success,失败返回 failure。 - 进阶题(难度⭐⭐):定义
AppError基类和DatabaseError(query 属性)、AuthError(statusCode 属性)子类。写一个函数根据不同错误类型返回不同的 HTTP 状态码和消息。 - 挑战题(难度⭐⭐⭐):实现
tryAsync<T>(fn: () => Promise<T>): Promise<Result<T, AppError>>,把异步操作的异常统一转换为 AppError 体系(区分网络错误、超时错误、服务器错误)。写一个 fetchWithRetry 函数,失败时自动重试。