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}`);
  }
}
逻辑代码 50 行(超过 40 行限制,仅展示)

输出:

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 语句。

📖 小节

📝 作业

  1. 基础题(难度⭐):写一个函数 safeParse(json: string): Result<object, Error>,用 try/catch 包裹 JSON.parse,成功返回 success,失败返回 failure。
  2. 进阶题(难度⭐⭐):定义 AppError 基类和 DatabaseError(query 属性)、AuthError(statusCode 属性)子类。写一个函数根据不同错误类型返回不同的 HTTP 状态码和消息。
  3. 挑战题(难度⭐⭐⭐):实现 tryAsync<T>(fn: () => Promise<T>): Promise<Result<T, AppError>>,把异步操作的异常统一转换为 AppError 体系(区分网络错误、超时错误、服务器错误)。写一个 fetchWithRetry 函数,失败时自动重试。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏