← 中文资源

简体中文版本

Result 类型化异步错误处理

用 Result 联合类型替代抛异常表达可预期失败:异步操作链式传递成功或错误,调用方按分支消费,无需 try-catch 金字塔。

阅读英文原文

示例说明

异常适合不可预期的程序性错误,业务失败混进异常流会丢失类型信息且强制所有调用方 try-catch。Result 联合把失败变成普通返回值:ok 标志判别分支,错误对象保持类型,可像数据一样传递、缓存与映射。mapResult 这类组合子让链式变换在错误时自动短路,金字塔回调消失。

技术要点

  • ok 判别分支保持类型收窄
  • 错误类型参数化保留具体错误类别
  • 组合子在错误路径自动短路

代码解读

fetchUser 对非法 ID 返回 ValidationError 类型的错误分支,HTTP 失败返回通用错误;两者都通过 err 构造器装箱。mapResult 只在成功分支执行变换函数,错误原样透传。消费端的三分支演示按错误类型分类处理:字段校验问题、上游失败各有归口。

使用建议

不要用 Result 表达宕机级错误,那仍该抛出。组合子按需增加,别造大型函数式库。与 Promise.all 组合时注意错误汇聚策略。

代码示例

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

const ok = <T>(value: T): Result<T, never> => ({ ok: true, value });
const err = <E>(error: E): Result<never, E> => ({ ok: false, error });

class ValidationError {
  constructor(readonly field: string, readonly reason: string) {}
}

async function fetchUser(id: string): Promise<Result<{ id: string; email: string }, Error>> {
  if (!/^[a-z0-9-]+$/.test(id)) {
    return err(new ValidationError('id', 'invalid characters'));
  }
  const res = await fetch('https://api.example.com/users/' + id);
  if (!res.ok) {
    return err(new Error('upstream status ' + res.status));
  }
  return ok(await res.json());
}

async function mapResult<T, U, E>(
  result: Result<T, E>, fn: (value: T) => Promise<U>,
): Promise<Result<U, E>> {
  if (!result.ok) return result;
  return ok(await fn(result.value));
}

const user = await fetchUser('u-123');
const decorated = await mapResult(user, async (u) => ({ ...u, email: u.email.toUpperCase() }));

if (decorated.ok) {
  console.log('email:', decorated.value.email);
} else if (decorated.error instanceof ValidationError) {
  console.log('field problem:', decorated.error.field);
} else {
  console.log('failure:', decorated.error.message);
}