You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript使用元组实现Either类型进行错误处理的方案

实现方案

完全可以通过TypeScript原生的联合元组类型实现符合要求的Either类型,无需额外依赖,TypeScript可基于控制流自动完成类型收窄,完全满足「错误/返回值有且仅有一个、校验后无断言安全访问」的需求。

核心类型定义

/**
 * 表示要么返回业务值、要么返回错误的元组类型
 * @template T 成功时的业务值类型
 * @template E 失败时的错误类型,默认继承原生Error
 */
type Either<T, E extends Error = Error> = [T, null] | [null, E];

该类型通过两个互斥的元组分支做严格约束:

  • 成功分支:第一个元素为业务值,第二个元素固定为null
  • 失败分支:第一个元素固定为null,第二个元素为错误对象
    编译期会直接拦截所有不符合约束的返回值,既不允许两个元素同时为有效值,也不允许两个元素同时为空。

完整使用示例

以用户创建场景为例:

// 业务类型定义
interface User {
  id: number;
  name: string;
  email: string;
}

class UserError extends Error {
  constructor(message: string, public code?: number) {
    super(message);
    this.name = 'UserError';
  }
}

// 业务函数实现
async function createUser(userData: Omit<User, 'id'>): Promise<Either<User, UserError>> {
  // 参数校验失败场景
  if (!userData.email.includes('@')) {
    return [null, new UserError('邮箱格式非法', 400)];
  }
  // 业务成功场景
  const newUser: User = {
    id: Date.now(),
    ...userData
  };
  return [newUser, null];
}

// 调用示例
async function runCreateUserFlow() {
  const [user, userError] = await createUser({ name: '张三', email: 'zhangsan@example.com' });

  // 未做错误校验时直接访问user属性会触发TS类型错误,符合预期
  // console.log(user.name); // 编译报错:user可能为null

  if (userError) {
    // 错误分支内TS自动收窄类型:user为null,userError为UserError类型
    console.error(`创建失败,错误码:${userError.code},错误信息:${userError.message}`);
    throw userError;
  }

  // 错误校验通过后,TS自动收窄类型:userError为null,user为User类型
  // 无需任何类型断言,可直接安全访问user的属性
  console.log(`用户创建成功,用户名:${user.name},用户ID:${user.id}`);
}

注意事项

  • 不要用可选元组(如[T?, E?])实现该类型:可选标记会允许元组元素空缺,无法保证「有且仅有一个有效值」的约束,也无法正确触发控制流类型收窄。
  • 建议用null作为无值占位:相比undefined,null的语义更明确,即使在strictNullChecks未完全开启的场景下,也能保持更稳定的类型推导表现。
  • 自定义错误类型建议继承原生Error:符合JS/TS的错误处理惯例,可保留错误栈、错误名等原生调试信息。

内容的提问来源于stack exchange,提问作者Diyan Slavov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 16:24:29