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

TypeScript中throw类型收窄失效:互斥返回类型处理疑问

TypeScript处理互斥返回类型的类型断言问题

我尝试创建可处理两种互斥返回类型的函数,但对TypeScript的工作机制存在误解。代码定义了SuccessResponse、ErrorResponse和Command类型,编写myFunc函数时,通过判断result.error !== null并抛出错误后,返回result.data却出现类型错误:Type 'T | null' is not assignable to type 'T'。由于T可以为null,无法仅通过if(result.data)进行判断。此外还提供了包含动机说明的完整示例代码,解释需要错误类型的原因是为了让类型工具断言并存储供后续使用。

问题代码片段

type SuccessResponse<T> = { data: T; error: null };
type ErrorResponse<E> = { data: null; error: NonNullable<E> };
type Command<T, E> = () => ErrorResponse<E> | SuccessResponse<T>;

const myFunc = <T, E>(c: Command<T, E>): T => { 
  const result = c();
  if (result.error !== null) throw result.error;
  return result.data;
}

错误信息

Type 'T | null' is not assignable to type 'T'.
  'T' could be instantiated with an arbitrary type which could be unrelated to 'T | null'.(2322)

完整动机示例代码

type SuccessResponse<T> = { data: T; error: null };
type ErrorResponse<E> = { data: null; error: NonNullable<E> };
type Command<T, E> = () => Promise<ErrorResponse<E> | SuccessResponse<T>>;

const myCommand = async () => {
  if (Math.random() < .1) return {data: null, error: new Error('Random error')};
  return await { data: Math.random(), error: null};
}

const getCommandResult = async <T, E>(c: Command<T, E>): Promise<T> => {
  const result = await c();
  if (result.error !== null) throw result.error;
  return result.data;
}

const makeRefreshable = <T, E>(f: () => Promise<T>) => {
  const result: {
    data: T | null;
    error: E | null;
    run: () => void;
  } = {
    data: null,
    error: null,
    run: () => {},
  }

  result.run = async () => {
    try {
      result.data = await f();
    } catch (err) {
      result.data = null;
      result.error = err as E;
    }
  }

  return result;
}

const main = async () => {
  const result = makeRefreshable<number, Error>(() => getCommandResult(myCommand));
  await result.run();
  if (result.data) console.log(result.data);
  if (result.error) console.log(result.error.message);
  await result.run();
  if (result.data) console.log(result.data);
  if (result.error) console.log(result.error.message);
}

main();

问题原因

TypeScript无法自动关联SuccessResponse和ErrorResponse中data与error的互斥关系——即使你通过result.error !== null排除了错误情况,它依然无法推断出此时result.data一定不是null。这是因为当前的联合类型没有明确的可辨识特征,TypeScript无法精准缩小类型范围。

解决方案

方案1:使用可辨识联合类型(推荐)

给两种响应类型添加一个共同的可辨识标签字段(比如type),让TypeScript能通过这个标签准确判断当前的响应类型:

type SuccessResponse<T> = { type: 'success'; data: T; error: null };
type ErrorResponse<E> = { type: 'error'; data: null; error: NonNullable<E> };
type Command<T, E> = () => ErrorResponse<E> | SuccessResponse<T>;

const myFunc = <T, E>(c: Command<T, E>): T => { 
  const result = c();
  if (result.type === 'error') throw result.error;
  // TypeScript此时能确定result是SuccessResponse<T>,data类型为T
  return result.data;
}

方案2:类型断言(快速修复)

如果无法修改原有类型定义,可以用类型断言直接告诉TypeScript当前result.data的类型:

const myFunc = <T, E>(c: Command<T, E>): T => { 
  const result = c();
  if (result.error !== null) throw result.error;
  return result.data as T;
}

注意:类型断言需要开发者自行保证逻辑的正确性,确保此时result.data确实符合T类型。

修正后的完整动机代码(使用可辨识联合)

type SuccessResponse<T> = { type: 'success'; data: T; error: null };
type ErrorResponse<E> = { type: 'error'; data: null; error: NonNullable<E> };
type Command<T, E> = () => Promise<ErrorResponse<E> | SuccessResponse<T>>;

const myCommand = async () => {
  if (Math.random() < .1) return { type: 'error', data: null, error: new Error('Random error')};
  return { type: 'success', data: Math.random(), error: null};
}

const getCommandResult = async <T, E>(c: Command<T, E>): Promise<T> => {
  const result = await c();
  if (result.type === 'error') throw result.error;
  return result.data;
}

const makeRefreshable = <T, E>(f: () => Promise<T>) => {
  const result: {
    data: T | null;
    error: E | null;
    run: () => void;
  } = {
    data: null,
    error: null,
    run: () => {},
  }

  result.run = async () => {
    try {
      result.data = await f();
    } catch (err) {
      result.data = null;
      result.error = err as E;
    }
  }

  return result;
}

const main = async () => {
  const result = makeRefreshable<number, Error>(() => getCommandResult(myCommand));
  await result.run();
  if (result.data) console.log(result.data);
  if (result.error) console.log(result.error.message);
  await result.run();
  if (result.data) console.log(result.data);
  if (result.error) console.log(result.error.message);
}

main();

内容的提问来源于stack exchange,提问作者Conley Owens

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 11:54:51