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

如何将回调函数转换为支持正确返回类型推断的async/await函数?

实现TypeScript类型安全的awaitify工具(支持自动推断返回类型)

我明白你遇到的问题了——原来的awaitify用ReturnType<F>来推断Promise的返回类型,但其实像foo这类带回调的函数,它的返回值是void,所以类型推断完全不对。咱们一步步拆解,实现一个能正确识别回调中value类型的版本。

核心思路

我们的目标很明确:对于遵循**Node.js风格回调(最后一个参数是(err, value) => void)**的函数,awaitify(func, ...args)要返回Promise<Value>,其中Value是回调函数第二个参数的类型。要做到这一点,需要分三步提取类型:

  1. 从原函数的参数列表中,提取最后那个回调函数的类型
  2. 从回调函数类型中,提取成功值value的类型
  3. 确保awaitify接收的参数是原函数去掉回调后的所有参数

逐步实现与解释

1. 提取回调函数的类型

首先,我们需要从原函数F的参数里,拿到最后那个回调的类型。用TypeScript的条件类型和infer关键字可以轻松做到:

// 提取函数F的最后一个参数(即回调函数)的类型
type CallbackType<F extends (...args: any[]) => any> = 
  Parameters<F> extends [...infer _RestArgs, infer Callback] 
    ? Callback 
    : never;
  • Parameters<F>会返回F的所有参数组成的数组类型
  • [...infer _RestArgs, infer Callback]把数组拆分成“前面所有参数”(用_RestArgs忽略)和“最后一个参数”(即回调Callback)

2. 从回调中提取成功值的类型

拿到回调类型后,我们要提取它第二个参数(也就是成功时的value)的类型:

// 从回调函数类型中提取value的类型
type ResolvedValue<F extends (...args: any[]) => any> = 
  CallbackType<F> extends (err: any, value: infer Value) => any 
    ? Value 
    : never;
  • 这里检查回调是否符合(err, value) => ...的结构,用infer Value捕获第二个参数的类型
  • 如果回调不符合这个结构,就返回never(相当于类型报错,提示输入不符合规范)

3. 定义awaitify的参数类型

我们需要让awaitify接收原函数去掉回调后的所有参数,所以定义一个类型来提取这些参数:

// 提取原函数去掉最后一个回调参数后的参数列表
type ArgsWithoutCallback<F extends (...args: any[]) => any> = 
  Parameters<F> extends [...infer Args, any] 
    ? Args 
    : never;

这个和你原来的Head类型作用一致,但语义更清晰,一眼就能看出是“去掉回调后的参数”。

4. 完整的awaitify实现

把上面的类型整合起来,写出最终的函数:

type CallbackType<F extends (...args: any[]) => any> = 
  Parameters<F> extends [...infer _RestArgs, infer Callback] 
    ? Callback 
    : never;

type ResolvedValue<F extends (...args: any[]) => any> = 
  CallbackType<F> extends (err: any, value: infer Value) => any 
    ? Value 
    : never;

type ArgsWithoutCallback<F extends (...args: any[]) => any> = 
  Parameters<F> extends [...infer Args, any] 
    ? Args 
    : never;

export function awaitify<F extends (...args: any[]) => any>(
  func: F,
  ...args: ArgsWithoutCallback<F>
): Promise<ResolvedValue<F>> {
  return new Promise((resolve, reject) => {
    func(...args, (err: Error | null, value: ResolvedValue<F>) => {
      if (err) {
        reject(err);
      } else {
        resolve(value);
      }
    });
  });
}

验证效果

用你给出的foo函数测试一下:

function foo(a: string | null, b: number | null, c: string[] | null, callback: (err: Error | null, value: string | number | string[]) => void) {
  if (a) {
    callback(null, a);
  } else if (b) {
    callback(null, b);
  } else if (c) {
    callback(null, c);
  } else {
    callback(new Error("error"));
  }
}

// 现在result会被正确推断为string | number | string[]
const result = await awaitify(foo, null, null, ["1", "2", "3"]);
// 当传入不同参数时,类型会自动缩小:
const stringResult = await awaitify(foo, "hello", null, null); // string类型
const numberResult = await awaitify(foo, null, 123, null); // number类型

可以看到,TypeScript现在能准确根据传入的参数,推断出awaitify返回的Promise的具体类型了。

为什么原来的实现不行?

你原来的代码用了ReturnType<F>,但F是像foo这样的函数——它的返回值是void(因为这类回调函数本身不返回值,结果通过回调传递),所以Promise<ReturnType<F>>就变成了Promise<void>,完全不符合我们的预期。这就是类型推断失败的核心原因。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 10:02:46