首页 / TypeScript 入门教程 / 错误处理与类型

TypeScript 入门教程

错误处理与类型

本教程共 80 篇 · 第 77 篇 · 更新于 2026-08-10 · 约 14 分钟阅读

TypeScriptTypeScript 入门教程错误处理try catchErrorResultuseUnknownInCatchVariables

本节目标:把错误处理里的类型问题讲透——为什么 TS 7.0 下 catcherrunknown、怎么安全地使用它、如何设计自定义 Error 类、以及用 Result<T, E> 模式把错误从”抛”变成”值”的利与弊。

useUnknownInCatchVariables

这是对 TS 错误处理影响最大的一个配置项。TS 7.0 的 strict: true 默认开启了它:

{
  "compilerOptions": {
    "strict": true // 自动开启 useUnknownInCatchVariables
  }
}

开启后,catch 的变量从 any 变成了 unknown

try {
  throw new Error("出错了");
} catch (err) {
  // TS 7.0 strict 下 err 类型:unknown
  console.error(err.message); // ❌ 'err' is of type 'unknown'
}

为什么这样做?因为在 JavaScript 里,throw 可以抛出任何值:

try {
  throw 42;         // 数字
  throw "error";    // 字符串
  throw undefined;  // undefined
  throw null;       // null
} catch (err) {
  // err 可能是任何类型,不能假设它有 .message
}

any 太宽松了,你会不假思索地写 err.message,运行时却抛出一个 “message” is not defined 的错误。unknown 强迫你在使用前做类型收窄。

处理 unknown 错误的正确姿势

标准做法是用 instanceof 检查:

try {
  riskyOperation();
} catch (err) {
  if (err instanceof Error) {
    // 这里 err: Error —— 安全访问 .message、.stack
    console.error(err.message);
  } else {
    // 非标准错误,手动转成字符串
    console.error("未知错误:", String(err));
  }
}
function getErrorMessage(err: unknown): string {
  if (err instanceof Error) return err.message;
  return String(err);
}

try {
  JSON.parse("{ bad json }");
} catch (err) {
  const msg = getErrorMessage(err);
  console.error(msg); // "Unexpected token b in JSON at position 2"
}

如果你确定只抛 Error

你也可以用类型断言,但断言过多说明你的错误处理不规范:

try {
  doSomething();
} catch (err) {
  const error = err as Error; // 断言:我确定只抛 Error
  console.error(error.message);
}

更好的做法是编码规范里约定只抛 Error 实例,这样 instanceof Error 就能覆盖所有情况。

关闭 useUnknownInCatchVariables

不推荐,但如果你有大量旧代码且不想改,可以关掉:

{
  "compilerOptions": {
    "useUnknownInCatchVariables": false
  }
}

关掉后 catch (err)err 回到 any,能写 err.message 不报错——但失去了编译期保护。

Error 类的类型体系

回顾一下上一章提到的 Error 类型体系:

// 内置 Error 子类
new Error("generic error");          // Error
new TypeError("type mismatch");       // TypeError extends Error
new RangeError("index out of range"); // RangeError extends Error
new SyntaxError("bad syntax");        // SyntaxError extends Error
new ReferenceError("not defined");    // ReferenceError extends Error
new URIError("bad URI");             // URIError extends Error
new EvalError("eval failed");        // EvalError extends Error

类型判断也按继承链走:

const te = new TypeError("oops");

console.log(te instanceof TypeError); // true
console.log(te instanceof Error);     // true(因为 TypeError extends Error)

自定义 Error 子类

业务里经常需要自定义 Error,携带更多上下文:

class ApiError extends Error {
  constructor(
    message: string,
    public statusCode: number,
    public responseBody?: unknown
  ) {
    super(message);
    this.name = "ApiError";
    // TS 7.0 target 为 ES2015+,不需要手动修复原型链
  }
}

async function fetchData(url: string): Promise<any> {
  const response = await fetch(url);
  if (!response.ok) {
    const body = await response.text();
    throw new ApiError(`请求失败: ${url}`, response.status, body);
  }
  return response.json();
}

async function main() {
  try {
    const data = await fetchData("/api/users");
  } catch (err) {
    if (err instanceof ApiError) {
      // err: ApiError —— 能访问 statusCode、responseBody
      console.error(`HTTP ${err.statusCode}: ${err.message}`);
      if (err.responseBody) {
        console.error("响应体:", err.responseBody);
      }
    } else if (err instanceof Error) {
      console.error("其他错误:", err.message);
    }
  }
}

注意 instanceof 检查的顺序:先检查更具体的 ApiError,再检查 Error。因为 ApiError extends ErrorApiError 实例同时满足两个检查——顺序反了就会进错分支。

Tip

TS 7.0 默认 target 是 ES2015,而 ES2015 的 class 语法会自动正确处理原型链。所以不需要像老教程那样写 Object.setPrototypeOf(this, ApiError.prototype)

自定义 Error 的链式结构

class NetworkError extends Error {
  constructor(message: string, public cause?: Error) {
    super(message);
    this.name = "NetworkError";
  }
}

class DatabaseError extends Error {
  constructor(message: string, public cause?: Error) {
    super(message, { cause }); // ES2022 的 error cause
    this.name = "DatabaseError";
  }
}

Error 构造函数的第二个参数支持 { cause }(ES2022),可以用来链式记录原始错误。TS 7.0 完全支持此语法。

Result<T, E> 模式简介

try/catch 有个先天不足:错误类型不在函数签名里。你看一个函数声明 function parse(data: string): User,完全不知道它会不会抛错、抛什么错。

Result<T, E> 模式来自 Rust 等函数式语言,把错误从”抛”变成”返回值”:

// Result 的定义
type Result<T, E = Error> =
  | { ok: true; value: T }
  | { ok: false; error: E };

// 成功
function ok<T>(value: T): Result<T, never> {
  return { ok: true, value };
}

// 失败
function err<E>(error: E): Result<never, E> {
  return { ok: false, error };
}

使用方式:

function parseJSON(text: string): Result<unknown, SyntaxError> {
  try {
    return ok(JSON.parse(text));
  } catch (e) {
    if (e instanceof SyntaxError) {
      return err(e);
    }
    throw e; // 非 SyntaxError 继续往外抛
  }
}

const result = parseJSON('{"name": "Alice"}');

if (result.ok) {
  console.log(result.value); // unknown
} else {
  console.error(result.error.message); // SyntaxError
}

Result 的优点

  1. 错误类型在签名里——一眼看到可能返回什么错误
  2. 调用方被迫处理——result.ok 检查是编译期强制的
  3. 不用 try/catch——控制流更线性

Result 的缺点

  1. 不是 TS/JS 的惯用风格——团队可能不熟悉
  2. 和 Promise 不兼容——async/await 没法直接和 Result 配合
  3. 需要额外包装层——每个函数都要 wrap

折中方案:只在关键边界(如数据解析、API 适配层)用 Result,内部维持 throw/catch。

Result 与 async

如果想在 async 函数中用,可以定义 AsyncResult<T, E>

type AsyncResult<T, E = Error> = Promise<Result<T, E>>;

async function fetchUser(id: number): AsyncResult<User, NetworkError> {
  try {
    const res = await fetch(`/api/user/${id}`);
    if (!res.ok) return err(new NetworkError(`HTTP ${res.status}`));
    const data = await res.json();
    return ok(data as User);
  } catch (e) {
    return err(new NetworkError("网络请求失败"));
  }
}

async function main() {
  const result = await fetchUser(1);
  if (result.ok) {
    console.log(result.value.name);
  } else {
    console.error(result.error.message);
  }
}

neverthrow 等第三方库

社区已有成熟的 Result 实现,比如 neverthrow

import { ok, err, Result } from "neverthrow";

function divide(a: number, b: number): Result<number, string> {
  if (b === 0) return err("除数不能为 0");
  return ok(a / b);
}

const result = divide(10, 2);

result.match(
  (value) => console.log("结果:", value),
  (error) => console.error("错误:", error)
);

neverthrow 提供了 mapandThenmatchunwrapOr 等函数式操作,比手写 if (result.ok) 更优雅。但对入门教程来说,先理解 Result<T, E> 的核心思想就够了。

小结

  • TS 7.0 strict 下 catch (err)err 类型是 unknown,使用前必须收窄
  • instanceof Error 是最标准的收窄方式;也可以统一约定只抛 Error 实例
  • 自定义 Error 子类可以携带业务上下文(状态码、字段名、原始错误等)
  • instanceof 检查时先检查子类再检查父类,顺序很重要
  • Result<T, E> 把错误从”抛”变成”返回值”,让错误类型出现在函数签名里
  • Result 在 TS 生态中不惯用,但适合在关键边界处引入
  • 成熟的 Result 库如 neverthrow 提供了函数式 API,可按需选用