微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

在 Typescript 中使用函数作为头等值时,有没有办法传递泛型绑定?

如何解决在 Typescript 中使用函数作为头等值时,有没有办法传递泛型绑定?

我正在尝试包装打字稿函数,返回添加额外行为的新函数。例如,在下面的最小重现中,(参见 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() 作用于通用的输入函数。因此,如果 fF 类型的函数,并且 const g = wrapFn(f),则可以编写 wrapFn() 使得 gG 类型,其中 F 的任何泛型类型参数已转移到 G

您可以阅读 microsoft/TypeScript#30215 以了解此高阶类型推断的工作原理以及获得此行为所需遵循的规则。特别是,此功能要求您为函数参数(例如,A extends any[])和返回(例如,R)具有单独的类型参数。它使用像 F extends (...args: any[])=>any 这样的泛型函数类型,您可以使用 conditional 实用程序类型(如 Parameters<F>ReturnType<F>)来提取参数类型或返回从中键入。

因此,如果您将 WrappedFnwrapFn 更改为以下内容:

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

Playground link to code

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。