我正在考虑编写一些函数的"简化"类似功能,并且无法理解如何获取除看起来像回调的参数之外的所有参数(它们通常有两个参数,看起来像这样:(error, result) => void.

这些函数位于不同的模块中,并在.d.ts文件中定义.这意味着获取这些参数将非常有用,因为其中一些参数作为私有类型位于模块内部.

这样的函数类型的例子(你可以注意到回调可以在任何地方,也可以有多个回调):

type FuncLoad1 = (arg1: string, arg2: string, callback: (error: string | null, result: string | null) => void) => void;

type FuncLoad2 = (arg1: string, callback: (error: string | null, result: string | null) => void, arg2: string) => void;

type FuncLoad3 = (arg1: string, callbackInProgress: (progress: number) => void, arg2: string, callbackOnCompleted: (error: string | null, result: string | null) => void) => void;

有没有可能从上面的一个中自动获得这种类型?

(arg1: string, arg2: string) => Promise<string | null>

正如您所注意到的,可以有任意数量的回调,它们可以在任何地方.不是函数的参数是已处理类型中剩下的所有参数.

推荐答案

不幸的是,没有合理的方法来实现这一点.


编写一个类型函数来从tuple type中删除所有类似回调的参数非常容易:

type TupleExclude<T extends any[], U, A extends any[] = []> =
  T extends [infer F, ...infer R] ? TupleExclude<R, U, F extends U ? A : [...A, F]> : A;

然后,您可以拥有一个实用程序类型,它返回该参数列表中任何回调的第二个参数的Promise:

type Promisify<T extends (...args: any) => void> =
  (...args: TupleExclude<Parameters<T>, (e: any, r: any) => void>) =>
    Promise<Parameters<Extract<Parameters<T>[number], (e: any, r: any) => void>>[1]>;

type PFL1 = Promisify<FuncLoad1>;
// type PFL1 = (args_0: string, args_1: string) => Promise<string | null>

type PFL2 = Promisify<FuncLoad2>;
// type PFL2 = (args_0: string, args_1: string) => Promise<string | null>

type PFL3 = Promisify<FuncLoad3>;
// type PFL3 = (args_0: string, args_1: string) => Promise<string | null | undefined>

除了返回类型PFL3之外,这与您所要求的非常接近.这里的主要问题是,没有简单的方法来区分(progress: number) => void(error: string | null, result: string | null) => void类型,来说明第二种类型是您所关心的"那个"回调.前者只声明了一个参数,但声明了functions of fewer parameters are assignable to functions of more parameters个,这就是compatible个参数.

可以想象真正深入类型系统以试图找到该示例的"那个"回调,但是如果有多个两参数回调,会发生什么呢?您不能使用像errorresult这样的names个参数.参数名称在类型系统中故意不可见(请参见microsoft/TypeScript#55736).但即使你could这样做了,你也应该回过头来,试着想象一下implementingpromisify函数是这样工作的.


假设您想要编写以下形式的函数

declare function promisify<T extends (...args: any)=>void>(f: T): Promisify<T>;

这让你可以打电话给

declare const: fl3: FuncLoad3;
const pfl3 = promisify(fl3);
pfl3("abc", "def");

那么,它将需要一个运行时实现.该实现无法访问TypeScrip的类型系统,在运行JavaScript代码时,该类型系统为erased.因此,它必须使用参数"abc""def"以某种方式调用fl3.它如何知道fl3预计会有多少个回调以及将它们放在哪里?类型(arg1: string, callbackInProgress: (progress: number) => void, arg2: string, callbackOnCompleted: (error: string | null, result: string | null) => void) => void;在运行时并不存在,运行时的函数对象不会公开太多关于它接受哪些参数的信息(有the length property of functions个,但所有这些都告诉您所需参数的数量).只是在运行时没有足够的信息让您执行任何操作.

这就是问题的答案;这在类型系统中是不可能的,在运行时也是完全不可能的.


如果您想要将其转换为可能的内容,则必须想出某种方法来告诉您的函数回调的预期go 向.最简单的方法就是声明:,将会有一个回调,它会在最后,它会把结果作为第二个参数.如果你这样做了,事情就会非常直接地进行:

function promisify<A extends any[], R>(f:
  (...args: [...A, (error: any, result: R) => void]) => void
): (...args: A) => Promise<R>;
function promisify(f: Function) {
  return (...args: any) => new Promise((resolve, reject) => {
    f(...args, (err: any, result: any) => {
      if (err == null)
        resolve(result);
      else
        reject(err);
    })
  })
}

function funcLoad(arg1: string, arg2: string,
  cb: (error: string | null, result: string | null) => void) {
  setTimeout(() => cb(null, arg1 + " " + arg2), 1000);
}
const p = promisify(funcLoad);
// const p: (arg1: string, arg2: string) => Promise<string | null>
async function foo() {
  const val = await p("abc", "def");
  console.log(val?.toUpperCase());
}
foo(); // "ABC DEF"

您甚至可以编写一些代码,其中promisify接受某种类型的Options对象,该对象改变回调的数量和/或位置,并对其进行查询.但是,您可能只需要包装您的函数,以符合"端的单一回调"形式:

// FuncLoad3, not the expected form
function funcLoad3(arg1: string,
  callbackInProgress: (progress: number) => void, arg2: string,
  callbackOnCompleted: (error: string | null, result: string | null) => void) {
  setTimeout(() => {
    callbackInProgress(123);
    setTimeout(() => callbackOnCompleted(null, arg1 + " " + arg2), 1000);
  }, 1000);
}

// just wrap it
const wrapFunc3 = (arg1: string, arg2: string,
  cb: (error: string | null, result: string | null) => void) =>
  funcLoad3(arg1, (n) => console.log(n), arg2, cb);

// now it works
const p3 = promisify(wrapFunc3);
async function bar() {
  const val = await p3("ghi", "jkl");
  console.log(val?.toUpperCase());
}
bar(); // 123, then "GHI JKL"

但所有这些都偏离了所问的问题,所以我不会深入讨论这是如何运作的.

Playground link to code

Typescript相关问答推荐

根据另一个成员推断类成员的类型

为什么缩小封闭内部不适用于属性对象,但适用于声明的变量?

在Angular中注入多个BrowserModules,但在我的代码中只有一个

路由链接不会导航到指定router-outlet 的延迟加载模块

TypeScrip界面属性依赖于以前的属性-针对多种可能情况的简明解决方案

为什么Typescript不能推断类型?

如何编写在返回函数上分配了属性的类型安全泛型闭包

返回嵌套props 的可变元组

如何通过属性名在两个泛型数组中找到匹配的对象?

Typescript将键数组映射到属性数组

在Google授权后由客户端接收令牌

TaskListProps[]类型不可分配给TaskListProps类型

垫子工具栏不起作用的Angular 布线

VUE 3类型';字符串';不可分配给类型';参考<;字符串&>

为什么数字是`{[key:string]:UNKNOWN;}`的有效密钥?

Typescript循环函数引用

React中的效果挂钩在依赖项更新后不会重新执行

在Typescript 无法正常工作的 Three.js 中更新动画中的 GLSL 统一变量

React HashRouter,单击导航栏时页面重新加载出现问题

对象可能未定义(通用)