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

TypeScript:如何根据注册函数类型设置process方法的返回类型?

问题

我定义了一个Batch泛型类,包含register()和process()两个方法:register()用于关联同步/异步处理函数,process()批量处理数组元素。预期process()返回规则:

  • 注册异步函数时,返回Promise<(SuccessResponse | FailureResponse)[]>
  • 注册同步函数时,直接返回(SuccessResponse | FailureResponse)[]

代码运行正常,但TypeScript对process()的返回类型校验报错:注册同步处理器后,TS仍认为process()返回Promise类型。

原代码实现如下:

// Existing types (simplified)
type EventSourceDataClassTypes = "event" | "eventBatch" | "eventBatchError";

type ResultType = unknown;
type SuccessResponse = ["success", ResultType, EventSourceDataClassTypes];
type FailureResponse = ["fail", string, EventSourceDataClassTypes];

// New types
type AnyFunction = (...args: Array<any>) => any;
type AnyAsyncFunction = (...args: Array<any>) => Promise<any>;

type ProcessReturn<Func extends AnyFunction | AnyAsyncFunction> = 
  Func extends AnyAsyncFunction
    ? Promise<(SuccessResponse | FailureResponse)[]>
    : (SuccessResponse | FailureResponse)[];

class Batch<Func extends AnyFunction | AnyAsyncFunction> {
  private handler?: Func;
  private records: Array<string> = [];

  public constructor(records: Array<string> = []) {
    this.records = records;
  }

  public register(handler: Func): void {
    this.handler = handler;
  }

  public process(): ProcessReturn<Func> {
    if (!this.handler) {
      throw new Error("No handler registered");
    }

    const processedRecords: (SuccessResponse | FailureResponse)[] = [];
    for (const record of this.records) {
      processedRecords.push(this.handler(record));
    }

    if (this.handler.constructor.name === "AsyncFunction") {
      return Promise.all(processedRecords) as ProcessReturn<Func>;
    } else {
      return processedRecords as ProcessReturn<Func>;
    }
  }
}

使用示例中报错点:

const main = async () => {
  const batch = new Batch(["record1", "record2", "record3"]);

  const mySyncHandler = (record: unknown): SuccessResponse | FailureResponse => {
    if (record === "record2") {
      return ["fail", "Hello world", "event"];
    } else {
      return ["success", "Hello world", "event"];
    }
  };

  batch.register(mySyncHandler);
  let result: (SuccessResponse | FailureResponse)[];
  result = batch.process(); // <- TS报错:类型 'Promise<(SuccessResponse | FailureResponse)[]>' 不能赋值给类型 '(SuccessResponse | FailureResponse)[]'
};

原因分析

  1. 泛型推断固化:实例化Batch时,TS会将泛型Func推断为AnyFunction | AnyAsyncFunction(此时还未注册处理器),后续调用register时,泛型不会自动更新为具体的处理器类型,导致ProcessReturn<Func>始终解析为联合类型,TS无法准确判断实际返回值。

  2. 运行时检查与静态类型脱节:用this.handler.constructor.name === "AsyncFunction"做的运行时判断,TS静态类型系统无法识别该逻辑,只能依赖类型断言,但断言无法覆盖所有场景,导致类型推断失效。

  3. 处理器返回类型约束不足:原AnyFunction和AnyAsyncFunction未约束处理器返回类型必须是SuccessResponse | FailureResponse或其Promise,TS无法准确跟踪返回值类型。

解决方案

思路:基于处理器返回类型的条件泛型 + 类型守卫

调整泛型定义,让TS根据处理器返回类型自动推断process()返回类型,同时用类型守卫替代运行时构造函数检查,让TS识别分支类型。

修正后的完整代码

// Existing types (simplified)
type EventSourceDataClassTypes = "event" | "eventBatch" | "eventBatchError";

type ResultType = unknown;
type SuccessResponse = ["success", ResultType, EventSourceDataClassTypes];
type FailureResponse = ["fail", string, EventSourceDataClassTypes];

// 约束处理器类型:接收string,返回目标类型或其Promise
type Handler = (record: string) => SuccessResponse | FailureResponse | Promise<SuccessResponse | FailureResponse>;

// 条件类型:根据处理器返回值是否为Promise,推断process的返回类型
type ProcessReturn<H extends Handler> = 
  ReturnType<H> extends Promise<any>
    ? Promise<(SuccessResponse | FailureResponse)[]>
    : (SuccessResponse | FailureResponse)[];

// 类型守卫:判断处理器是否为异步函数
function isAsyncHandler(handler: Handler): handler is (record: string) => Promise<SuccessResponse | FailureResponse> {
  return handler.constructor.name === "AsyncFunction";
}

class Batch<H extends Handler = Handler> {
  private handler?: H;
  private records: string[] = [];

  constructor(records: string[] = []) {
    this.records = records;
  }

  // 泛型register方法:返回更新泛型后的Batch实例,让TS自动更新类型推断
  register<NewH extends Handler>(handler: NewH): Batch<NewH> {
    this.handler = handler as unknown as H;
    return this as unknown as Batch<NewH>;
  }

  process(): ProcessReturn<H> {
    if (!this.handler) {
      throw new Error("No handler registered");
    }

    if (isAsyncHandler(this.handler)) {
      // 异步分支:TS识别handler返回Promise,Promise.all结果为Promise数组
      return Promise.all(this.records.map(this.handler)) as ProcessReturn<H>;
    } else {
      // 同步分支:TS识别handler返回非Promise,直接返回数组
      return this.records.map(this.handler) as ProcessReturn<H>;
    }
  }
}

使用示例(无类型报错)

const main = async () => {
  const batch = new Batch(["record1", "record2", "record3"]);

  const mySyncHandler = (record: string): SuccessResponse | FailureResponse => {
    if (record === "record2") {
      return ["fail", "Hello world", "event"];
    } else {
      return ["success", "Hello world", "event"];
    }
  };

  const myAsyncHandler = async (record: string): Promise<SuccessResponse | FailureResponse> => {
    await new Promise((resolve) => setTimeout(resolve, 100));
    if (record === "record2") {
      return ["fail", "Hello world", "event"];
    } else {
      return ["success", "Hello world", "event"];
    }
  };

  // 注册同步处理器,TS自动推断Batch泛型为同步处理器类型
  const syncBatch = batch.register(mySyncHandler);
  const syncResult: (SuccessResponse | FailureResponse)[] = syncBatch.process(); // 类型正确
  console.log(syncResult);

  // 注册异步处理器,TS自动推断Batch泛型为异步处理器类型
  const asyncBatch = syncBatch.register(myAsyncHandler);
  const asyncResult: (SuccessResponse | FailureResponse)[] = await asyncBatch.process(); // 类型正确
  console.log(asyncResult);
};

main();

关键修改点说明

  1. 约束处理器类型:Handler明确限定输入为string,输出为目标响应类型或其Promise,确保处理器符合预期规范。

  2. 泛型更新机制:register改为泛型方法,返回更新泛型后的Batch实例,让TS在注册处理器后自动更新类型推断,准确识别process()返回类型。

  3. 类型守卫:isAsyncHandler让TS能识别异步处理器分支,避免依赖类型断言导致的类型不准确。

  4. 简化逻辑:用Array.map替代循环+push,代码更简洁,TS能更好跟踪数组类型。

替代思路:拆分同步/异步Batch类

如果不需要同一个实例切换同步/异步处理器,可拆分出SyncBatch和AsyncBatch,类型推断更直接:

// 同步Batch类
class SyncBatch {
  private handler: (record: string) => SuccessResponse | FailureResponse;
  private records: string[] = [];

  constructor(records: string[] = []) {
    this.records = records;
  }

  register(handler: (record: string) => SuccessResponse | FailureResponse): void {
    this.handler = handler;
  }

  process(): (SuccessResponse | FailureResponse)[] {
    if (!this.handler) throw new Error("No handler registered");
    return this.records.map(this.handler);
  }
}

// 异步Batch类
class AsyncBatch {
  private handler: (record: string) => Promise<SuccessResponse | FailureResponse>;
  private records: string[] = [];

  constructor(records: string[] = []) {
    this.records = records;
  }

  register(handler: (record: string) => Promise<SuccessResponse | FailureResponse>): void {
    this.handler = handler;
  }

  process(): Promise<(SuccessResponse | FailureResponse)[]> {
    if (!this.handler) throw new Error("No handler registered");
    return Promise.all(this.records.map(this.handler));
  }
}

这种方案类型清晰,无需复杂条件泛型,适合不需要动态切换处理器类型的场景。

内容的提问来源于stack exchange,提问作者Andre.IDK

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 06:02:13