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)[]' };
原因分析
泛型推断固化:实例化
Batch时,TS会将泛型Func推断为AnyFunction | AnyAsyncFunction(此时还未注册处理器),后续调用register时,泛型不会自动更新为具体的处理器类型,导致ProcessReturn<Func>始终解析为联合类型,TS无法准确判断实际返回值。运行时检查与静态类型脱节:用
this.handler.constructor.name === "AsyncFunction"做的运行时判断,TS静态类型系统无法识别该逻辑,只能依赖类型断言,但断言无法覆盖所有场景,导致类型推断失效。处理器返回类型约束不足:原
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();
关键修改点说明
约束处理器类型:
Handler明确限定输入为string,输出为目标响应类型或其Promise,确保处理器符合预期规范。泛型更新机制:
register改为泛型方法,返回更新泛型后的Batch实例,让TS在注册处理器后自动更新类型推断,准确识别process()返回类型。类型守卫:
isAsyncHandler让TS能识别异步处理器分支,避免依赖类型断言导致的类型不准确。简化逻辑:用
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

