如何为universalify生成的Promise/回调兼容函数编写TS类型定义?
这个问题其实很常见,在处理同时支持Promise和回调的"通用"函数时,重复写重载确实会带来不少维护负担。TypeScript的条件类型和交叉类型正好可以帮我们打造一个通用的类型工具,一次性解决所有这类函数的类型定义问题。
第一步:定义通用回调类型
先封装一个通用的回调类型,避免重复写繁琐的回调签名:
// 通用回调类型:错误固定为Error | null,结果为泛型TResult type Callback<TResult = void> = (error: Error | null, result: TResult) => void;
第二步:打造通用类型转换工具
创建一个可复用的类型工具,把普通的回调风格函数,自动转换成同时支持Promise调用和回调调用的重载类型:
// 通用类型:将回调风格函数转为双模式支持的函数类型 type Universalify<Fn> = Fn extends (...args: [...infer Args, Callback<infer TResult>]) => void ? // 交叉两种调用方式: // 1. 只传前置参数 → 返回Promise<TResult> // 2. 传全参数(含回调) → 遵循原函数的void返回 ((...args: Args) => Promise<TResult>) & Fn : never;
这个类型的工作逻辑很直观:
- 用条件类型
extends判断输入函数是否是「最后一个参数为回调」的结构 - 通过
infer提取出前置参数列表Args,以及回调返回的结果类型TResult - 交叉两个函数类型:TypeScript会把交叉的函数类型当作重载来处理,调用时自动匹配合适的签名
示例:重构你的someFunc
现在用这个工具重构你原来的重复重载:
// 第一步:定义原始的回调风格函数类型 type SomeFuncCallbackStyle = ( opts: object, callback: Callback<any | void> ) => void; // 第二步:生成双模式支持的函数类型 type SomeFunc = Universalify<SomeFuncCallbackStyle>; // 第三步:声明函数(无需再写重复重载) declare const someFunc: SomeFunc;
这样两种调用方式都会被TypeScript正确识别:
// 方式1:Promise调用 const result = await someFunc({ foo: "bar" }); // 方式2:回调调用 someFunc({ foo: "bar" }, (err, res) => { if (err) console.error(err); else console.log(res); });
扩展:支持多参数场景
如果你的函数有多个前置参数(不止一个opts),这个工具同样适用。比如一个接受两个参数+回调的乘法函数:
// 原始回调风格函数 type MultiplyCallbackStyle = ( a: number, b: number, callback: Callback<number> ) => void; // 生成双模式类型 type MultiplyFunc = Universalify<MultiplyCallbackStyle>; declare const multiply: MultiplyFunc; // 两种调用方式都合法 const product = await multiply(2, 3); multiply(2, 3, (err, res) => console.log(res)); // 输出6
为什么这比重复重载更好?
- 减少冗余代码:只需要定义一次原始回调类型,所有双模式函数都复用同一个
Universalify工具 - 维护更轻松:修改参数/返回类型时,只需改动原始回调类型,不用同步修改多个重载签名
- 类型更一致:避免手动写重载时可能出现的类型不一致问题
内容的提问来源于stack exchange,提问作者Richie Bendall
相关产品推荐
相关产品推荐

