不幸的是,没有合理的方法来实现这一点.
编写一个类型函数来从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个参数.
可以想象真正深入类型系统以试图找到该示例的"那个"回调,但是如果有多个两参数回调,会发生什么呢?您不能使用像error
和result
这样的names个参数.参数名称在类型系统中故意不可见(请参见microsoft/TypeScript#55736).但即使你could这样做了,你也应该回过头来,试着想象一下implementing和promisify
函数是这样工作的.
假设您想要编写以下形式的函数
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个