问题描述
我正在尝试包装打字稿函数,返回添加额外行为的新函数。例如,在下面的最小重现中,(参见 playground)包装后的函数总是返回一个 Promise 并且它的参数和返回值是控制台记录的。
但是,我发现如果原始函数有任何 Generic 类型,我不知道如何绑定该 Generic 类型,因此即使它在调用范围内是众所周知的,它也可以在包装函数上使用。
问题由以下脚本中的 const delayedValue
演示。打字稿似乎相信这只能是未知的。
有什么办法可以例如wrappedDelay
被定义为使得通用 V
参数可以通过,从而告知我们可以从 wrappedDelay
函数期望什么返回类型。
export type AnyFn = (...args: any[]) => any;
/** If sync function - ReturnType. If async function - unwrap Promise of ReturnType */
export type Result<Fn extends AnyFn> = Fn extends () => Promise<infer Promised>
? Promised
: ReturnType<Fn>;
/** Async function with same params and return as Fn,regardless if Fn is sync or async */
export type WrappedFn<Fn extends AnyFn> = (...parameters:Parameters<Fn>) => Promise<Result<Fn>>
/** Construct wrapped function from function reference */
function wrapFn<Fn extends AnyFn>(fn:Fn) : WrappedFn<Fn>{
return async (...parameters:Parameters<Fn>) => {
console.log(`Parameters are ${parameters}`);
const result = await fn(...parameters);
console.log(`Result is ${result}`);
return result;
}
}
function sum(a:number,b:number){
return a + b;
}
function delay<V>(value: V,ms:number){
return new Promise<V>((resolve,reject) => {
setTimeout(() => resolve(value),ms)
})
}
const wrappedSum = wrapFn(sum)
const wrappedDelay = wrapFn(delay)
async function example() {
const sum = await wrappedSum(3,4)
const delayedValue = await wrappedDelay("hello",1000)
}
解决方法
TypeScript 不直接支持 microsoft/TypeScript#1213 中请求的“高级类型”,因此没有通用方法来以编程方式操作泛型函数,例如表达一个纯类型级从函数类型 F
到函数类型 G
的转换,其中 F
上的任何泛型类型参数都被转移到 G
。
幸运的是,从 TypeScript 3.4 开始, 支持 higher order type inference from generic functions,您可以在 值级别为 特定 函数获得这样的行为,例如 wrapFn()
作用于通用的输入函数。因此,如果 f
是 F
类型的函数,并且 const g = wrapFn(f)
,则可以编写 wrapFn()
使得 g
是 G
类型,其中 F
的任何泛型类型参数已转移到 G
。
您可以阅读 microsoft/TypeScript#30215 以了解此高阶类型推断的工作原理以及获得此行为所需遵循的规则。特别是,此功能要求您为函数参数(例如,A extends any[]
)和返回(例如,R
)具有单独的类型参数。它不使用像 F extends (...args: any[])=>any
这样的泛型函数类型,您可以使用 conditional 实用程序类型(如 Parameters<F>
或 ReturnType<F>
)来提取参数类型或返回从中键入。
因此,如果您将 WrappedFn
和 wrapFn
更改为以下内容:
type WrappedFn<A extends any[],R> = (...parameters: A) => Promise<R>
function wrapFn<A extends any[],R>(fn: (...args: A) => R): WrappedFn<A,R> {
return async (...parameters: A) => {
console.log(`Parameters are ${parameters}`);
const result = await fn(...parameters);
console.log(`Result is ${result}`);
return result;
}
}
然后事情就会如您所愿,至少对于您问题中的示例代码:
const wrappedDelayValue = wrapFn(delay);
// const wrappedDelayValue: <V>(value: V,ms: number) => Promise<Promise<V>>
const delayedValue = await wrappedDelayValue("hello",1000);
// const delayedValue: string
console.log(delayedValue.toUpperCase()) // no compiler error now