具有通用可选参数的Typescript函数重载 [英] Typescript function overloads with generic optional parameters

查看:101
本文介绍了具有通用可选参数的Typescript函数重载的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图编写一个高阶函数来包装输入函数,并缓存最近一次调用的结果作为副作用.基本功能(withCache)看起来像这样:

I'm trying to write a higher-order function wraps the input function and caches the result of the most recent call as a side effect. The basic function (withCache) looks something like this:

function cache(key: string, value: any) {
    //Some caching logic goes here
}

function withCache<R>(key: string, fn: (...args: any[]) => R): (...args: any[]) => R {
    return (...args) => {
        const res = fn(...args);
        cache(key, res);
        return res;
    }
}

const foo = (x: number, y: number) => x + y;
const fooWithCache = withCache("foo", foo);
let fooResult1 = fooWithCache(1, 2); // allowed :)
let fooResult2 = fooWithCache(1, 2, 3, 4, 5, 6) // also allowed :(

现在,我知道我可以使用函数重载使这种类型的安全性达到一定程度:

Now I know I can make this type safe - up to a point - using function overloads, like so:

function withCache<R>(key: string, fn: () => R): () => R
function withCache<R, T1>(key: string, fn: (a: T1) => R): (a: T1) => R
function withCache<R, T1, T2>(key: string, fn: (a: T1, b: T2) => R): (a: T1, b: T2) => R
function withCache<R>(key: string, fn: (...args: any[]) => R): (...args: any[]) => R {
    // implementation ...
}

const foo = (x: number, y: number) => x + y;
const fooWithCache = withCache("foo", foo);
let fooResult1 = fooWithCache(1, 2); // allowed :)
let fooResult2 = fooWithCache(1, 2, 3, 4, 5, 6) // not allowed :)

当我尝试允许带有可选参数的函数时,麻烦就来了(最后一次重载是新的):

The trouble comes when I try to allow functions with optional arguments (the last overload is new):

function withCache<R>(key: string, fn: () => R): () => R
function withCache<R, T1>(key: string, fn: (a: T1) => R): (a: T1) => R
function withCache<R, T1, T2>(key: string, fn: (a: T1, b: T2) => R): (a: T1, b: T2) => R
function withCache<R, T1, T2>(key: string, fn: (a: T1, b?: T2) => R): (a: T1, b?: T2) => R
function withCache<R>(key: string, fn: (...args: any[]) => R): (...args: any[]) => R {
    // implementation ...
}

const foo = (x: number, y?: number) => x + (y || 0);
const fooWithCache = withCache("foo", foo);
let fooResult1 = fooWithCache(1); // allowed :)
let fooResult2 = fooWithCache(1, 2) // not allowed, but should be :(

问题似乎是Typescript为withCache选择了错误的重载,结果是fooWithCache的签名为(a: number) => number.我希望fooWithCache的签名是(a: number, b?: number) => number,就像foo一样. 有什么办法可以解决这个问题?

The problem seems to be that Typescript is picking the wrong overload for withCache, and the result is that the signature for fooWithCache is (a: number) => number. I would expect fooWithCache's signature to be (a: number, b?: number) => number, just like foo. Is there any way to fix this?

(请注意,有什么方法可以声明重载,所以我不必重复每个重载的函数类型(...) => R?)

想出了我关于不重复函数类型的第二个问题:只需对其进行定义即可!

Figured out my secondary question about not repeating the function type: just define it!

type Function1<T1, R> = (a: T1) => R;
// ...
function withCache<T1, R>(fn: Function1<T1, R>): Function1<T1, R>;

这对于异步功能如何工作(假设您要缓存结果而不是Promise本身)?您当然可以这样做:

How would this work for an asynchronous function (assuming you wanted to cache the result and not the Promise itself)? You could certainly do this:

function withCache<F extends Function>(fn: F) {
  return (key: string) =>
      ((...args) => 
        //Wrap in a Promise so we can handle sync or async
        Promise.resolve(fn(...args)).then(res => { cache(key, res); return res; })
    ) as any as F; //Really want F or (...args) => Promise<returntypeof F>
}

但是与同步函数一起使用将是不安全的:

But then it would be unsafe to use with a synchronous function:

//Async function
const bar = (x: number) => Promise.resolve({ x });
let barRes = withCache(bar)("bar")(1).x; //Not allowed :)

//Sync function
const foo = (x: number) => ({ x });
let fooRes = withCache(foo)("bar")(1).x; //Allowed, because TS thinks fooRes is an object :(

有没有办法防止这种情况发生?还是编写一个对两者都安全起作用的函数?

Is there a way to guard against this? Or to write a function that safely works for both?

摘要:@jcalz的答案是正确的.如果可以假定使用同步函数,或者可以直接使用Promises而不使用它们解析的值,则断言函数类型可能是安全的.但是,如果没有某些尚未实现的未实现,上述同步或异步方案就不可能实现 语言

Summary: @jcalz's answer is correct. In cases where synchronous functions can be assumed, or where it's okay to work with Promises directly and not the values they resolve to, asserting the function type is probably safe. However, the sync-or-async scenario described above is not possible without some as-yet unimplemented language improvements.

推荐答案

通过选择列表中的第一个匹配项,可以选择过载.

Overloads are chosen by going down the list and picking the first one that matches.

检查以下代码,这些代码可以成功编译:

Examine the following code, which successfully compiles:

declare let f: (a: any, b?: any) => void;
declare let g: (a: any) => void;
g = f; // okay

函数f是接受一个或两个参数的函数,而g被声明为接受一个或两个参数的函数.您可以将值f分配给变量g,因为可以在可以调用一个参数的函数的任何位置调用一个或两个参数的任何函数.正是因为第二个参数是 optional 才使该分配工作有效.

The function f is a function which accepts one or two parameters, while g is declared as a function which accepts one. You can assign the value f to the variable g, because you can call any function of one-or-two parameters anywhere you can call a function of one parameter. It is precisely the fact that the second parameter is optional that makes this assignment work.

您还可以执行其他任务:

You can also do the other assignment:

f = g; //okay

因为您可以在任何地方调用一个或两个参数的函数来调用一个参数的任何函数.这意味着这两种功能可以相互分配(即使它们不是等效的,这有点不合理).

because you can call any function of one parameter anywhere you can call a function of one-or-two parameters. That means that these function two types are mutually assignable (even though they are not equivalent, which is a bit of unsoundness).

如果我们只看这两个重载:

If we look only at these two overloads:

function withCache<R, T1>(key: string, fn: (a: T1) => R): (a: T1) => R
function withCache<R, T1, T2>(key: string, fn: (a: T1, b?: T2) => R): (a: T1, b?: T2) => R

上面对fg的讨论意味着,与那些重载之一匹配的任何东西都将与另一个重载匹配.因此,将首先选择您列出的任何一个.抱歉,您不能同时使用它们.

The above discussion of f and g implies that anything which matches one of those overloads will match the other. So whichever one you list first will be chosen. You can't actually use both of them, sorry.

在这一点上,我建议您开始考虑一组折衷的重载,这些重载可以提供合理的行为,但让我们备份一下:

At this point, I could suggest you start coming up with a compromise set of overloads that gives reasonable behavior, but let's back up:

您不只是想要类型安全的withCache()版本吗?怎么样:

Don't you just want a type-safe version of withCache()? How about this:

function withCache<F extends Function>(key: string, fn: F): F {     
    // implementation ...
}

没有重载,并且返回值总是与fn参数相同的类型:

No overloads, and the return value is always the same type as the fn parameter:

const foo = (x: number, y?: number) => x;
const fooWithCache = withCache("foo", foo); // (x: number, y?: number) => number
let fooResult1 = fooWithCache(1); // allowed :)
let fooResult2 = fooWithCache(1, 2) // allowed :)

这对您有用吗?祝你好运!

Does that work for you? Good luck!

这篇关于具有通用可选参数的Typescript函数重载的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆