是否可以从函数类型创建“承诺”类型,删除参数中看起来像回调的任何内容并保留其余部分?

问题描述 投票:0回答:1

我正在考虑编写一些“承诺”的函数等效项,但无法理解如何获取除那些看起来像回调的参数之外的所有参数(它们通常有 2 个参数,看起来像这样:

(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>

正如您所注意到的,可以有任意数量的回调,并且它们可以在任何地方。处理后的类型中仅剩下非函数的参数。

typescript async-await promise
1个回答
0
投票

不幸的是,没有合理的方法来实现上述目的。


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

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
类型来说明第二个是您关心的“回调”。前者仅声明单个参数,但较少参数的函数可以分配给较多参数的函数,这使得它们兼容

有可能想象真正挖掘类型系统以尝试找到该示例的“回调”,但是如果您有多个两个参数回调会发生什么?您不能使用 error

result
等参数的
names
。参数名称故意在类型系统中不可观察(请参阅 microsoft/TypeScript#55736)。但即使你可以做到这一点,你也应该备份并尝试想象实现一个以这种方式工作的
promisify
函数。


大概您想编写一个以下形式的函数

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

可以让你打电话

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

嗯,它需要一个运行时实现。该实现无法访问 TypeScript 的类型系统,该系统在 JavaScript 代码运行时已被“擦除”。所以它必须以某种方式使用参数 fl3

"abc"
来调用
"def"
。它如何知道
fl3
需要多少个回调以及将它们放在哪里?类型
(arg1: string, callbackInProgress: (progress: number) => void, arg2: string, callbackOnCompleted: (error: string | null, result: string | null) => void) => void;
在运行时不存在,并且运行时的函数对象不会公开太多关于它所采用的参数的信息(有
length 函数的属性
,但唯一能告诉你的是所需的数量参数)。运行时没有足够的信息供您执行任何操作。
这就是所问问题的答案;这在类型系统中实际上是不可能的,并且在运行时也是完全不可能的。

如果你想将其转换为可能的东西,你必须想出某种方法来告诉你的函数回调应该去哪里。最简单的事情就是声明:将有一个回调,并且将在最后,并且它将期望结果作为第二个参数。如果你这样做了,事情就会直接进行:

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

采用某种选项对象来更改回调的数量和/或位置并查阅它。但是你也可以将你的函数包装成符合“single-callback-at-end”形式:

// 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 代码链接

© www.soinside.com 2019 - 2024. All rights reserved.