正在加载,请稍候…

TypeScript 错误处理:Result 类型与 Never 模式

在 TypeScript 中使用 Result/Either 类型、可辨识联合、never 检查和结构化错误层次构建健壮的错误处理,替代到处使用 try/cat

TypeScript 错误处理:Result 类型与 Never 模式

TypeScript 错误处理:Result 类型与 Never 模式

try/catch 的问题

// 这个函数可能抛出什么错误?从签名中无法得知
async function getUser(id: string): Promise<User> {
  // 可能抛出:NotFoundError, DatabaseError, ValidationError, NetworkError
  return await userRepo.findById(id);
}

// 调用者必须猜测要捕获什么
try {
  const user = await getUser(id);
} catch (err) {
  // err 是什么类型?不阅读实现就无法知道
}

TypeScript 错误处理:Result 类型与 Never 模式示意图

Result 类型模式

type Ok<T> = { readonly ok: true; readonly value: T };
type Err<E> = { readonly ok: false; readonly error: E };
type Result<T, E = Error> = Ok<T> | Err<E>;

// 构造函数
const ok = <T>(value: T): Ok<T> => ({ ok: true, value });
const err = <E>(error: E): Err<E> => ({ ok: false, error });

// 使用 - 错误在类型签名中显式体现!
async function findUser(id: string): Promise<Result<User, UserError>> {
  try {
    const user = await db.findById(id);
    if (!user) return err({ type: 'NOT_FOUND', id } as const);
    return ok(user);
  } catch (e) {
    return err({ type: 'DB_ERROR', cause: e } as const);
  }
}

// 调用者显式处理错误
const result = await findUser('123');
if (!result.ok) {
  switch (result.error.type) {
    case 'NOT_FOUND': return res.status(404).json({ error: 'User not found' });
    case 'DB_ERROR': return res.status(500).json({ error: 'Database error' });
  }
}
const user = result.value; // TypeScript 知道这是 User 类型

TypeScript 错误处理:Result 类型与 Never 模式示意图

类型化错误层次结构

abstract class AppError extends Error {
  abstract readonly code: string;
  abstract readonly statusCode: number;

  constructor(message: string, readonly cause?: unknown) {
    super(message);
    this.name = this.constructor.name;
  }

  toJSON() {
    return {
      error: this.name,
      code: this.code,
      message: this.message,
    };
  }
}

class NotFoundError extends AppError {
  readonly code = 'NOT_FOUND';
  readonly statusCode = 404;

  constructor(resource: string, id: string) {
    super(`${resource} with id '${id}' not found`);
  }
}

class ValidationError extends AppError {
  readonly code = 'VALIDATION_ERROR';
  readonly statusCode = 400;

  constructor(readonly fields: Record<string, string>) {
    super('Validation failed');
  }
}

class UnauthorizedError extends AppError {
  readonly code = 'UNAUTHORIZED';
  readonly statusCode = 401;
  constructor() { super('Authentication required'); }
}

// 错误处理中间件
function errorHandler(err: unknown, req: Request, res: Response) {
  if (err instanceof AppError) {
    return res.status(err.statusCode).json(err.toJSON());
  }
  console.error('Unexpected error:', err);
  return res.status(500).json({ error: 'Internal server error' });
}

TypeScript 错误处理:Result 类型与 Never 模式示意图

Never 模式(穷举检查)

type PaymentMethod = 'credit_card' | 'paypal' | 'crypto';

function processPayment(method: PaymentMethod): void {
  switch (method) {
    case 'credit_card':
      processCreditCard();
      break;
    case 'paypal':
      processPaypal();
      break;
    case 'crypto':
      processCrypto();
      break;
    default:
      // TypeScript 确保这里不可达
      const _exhaustive: never = method;
      throw new Error(`Unhandled payment method: ${_exhaustive}`);
  }
}

// 现在如果你向 PaymentMethod 添加 'bank_transfer',
// TypeScript 会在 never 赋值处报错,
// 强制你处理新情况

使用 andThen 进行链式调用

class Result<T, E> {
  private constructor(
    private readonly _ok: boolean,
    private readonly _value?: T,
    private readonly _error?: E
  ) {}

  static ok<T, E>(value: T): Result<T, E> { return new Result(true, value); }
  static err<T, E>(error: E): Result<T, E> { return new Result(false, undefined, error); }

  get ok(): boolean { return this._ok; }
  get value(): T { if (!this._ok) throw new Error('Result is Err'); return this._value!; }
  get error(): E { if (this._ok) throw new Error('Result is Ok'); return this._error!; }

  map<U>(fn: (value: T) => U): Result<U, E> {
    if (this._ok) return Result.ok(fn(this._value!));
    return Result.err(this._error!);
  }

  andThen<U>(fn: (value: T) => Result<U, E>): Result<U, E> {
    if (this._ok) return fn(this._value!);
    return Result.err(this._error!);
  }
}

// 链式操作
const result = await findUser(userId)
  .andThen(user => validateUserAge(user))
  .andThen(user => checkSubscription(user))
  .map(user => UserDto.from(user));

将错误纳入类型系统可以在编译时捕获错误处理问题。