eth*_*day 5 generics higher-order-functions typescript
我正在尝试编写一个高阶函数来包装输入函数并缓存最近调用的结果作为副作用。基本函数 ( withCache) 看起来像这样:
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 :(
Run Code Online (Sandbox Code Playgroud)
现在我知道我可以使用函数重载使这种类型安全 - 在一定程度上 - 如下所示:
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 :)
Run Code Online (Sandbox Code Playgroud)
当我尝试允许带有可选参数的函数时,麻烦就来了(最后一个重载是新的):
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 :(
Run Code Online (Sandbox Code Playgroud)
问题似乎是 Typescript 为 选取了错误的重载withCache,结果是 的签名fooWithCache为(a: number) => number。我期望fooWithCache的签名是(a: number, b?: number) => number,就像foo。有没有什么办法解决这一问题?
(顺便说一句,有没有办法声明重载,这样我就不必重复每个重载的函数类型(...) => R?)
解决了我关于不重复函数类型的第二个问题:只需定义它即可!
type Function1<T1, R> = (a: T1) => R;
// ...
function withCache<T1, R>(fn: Function1<T1, R>): Function1<T1, R>;
Run Code Online (Sandbox Code Playgroud)
对于异步函数来说,这将如何工作(假设您想缓存结果而不是 Promise 本身)?你当然可以这样做:
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>
}
Run Code Online (Sandbox Code Playgroud)
但与同步函数一起使用是不安全的:
//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 :(
Run Code Online (Sandbox Code Playgroud)
有办法防范这种情况吗?或者编写一个对两者都安全工作的函数?
摘要:@jcalz 的答案是正确的。在可以假定同步函数的情况下,或者可以直接使用 Promise 而不是它们解析的值的情况下,断言函数类型可能是安全的。然而,如果没有一些尚未实现的 语言 改进,上述同步或异步场景是不可能实现的。
通过向下浏览列表并选择第一个匹配的重载来选择重载。
检查以下代码,该代码已成功编译:
declare let f: (a: any, b?: any) => void;
declare let g: (a: any) => void;
g = f; // okay
Run Code Online (Sandbox Code Playgroud)
函数f是接受一个或两个参数的函数,而g声明为接受一个参数的函数。您可以将值赋给f变量g,因为您可以在可以调用一个参数的函数的任何地方调用任何一个或两个参数的函数。正是第二个参数是可选的这一事实使得该分配起作用。
您还可以做其他作业:
f = g; //okay
Run Code Online (Sandbox Code Playgroud)
因为您可以在任何可以调用一两个参数的函数的地方调用一个参数的任何函数。这意味着这两种类型的函数是可以相互赋值的(尽管它们并不等价,这有点不合理)。
如果我们只看这两个重载:
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
Run Code Online (Sandbox Code Playgroud)
f上面对和 的讨论g意味着任何与这些重载之一匹配的内容都将与另一个重载匹配。因此,无论您先列出哪个,都会被选择。抱歉,您实际上无法同时使用它们。
此时,我可以建议您开始提出一组折衷的重载,以提供合理的行为,但让我们备份一下:
您不只想要一个类型安全的版本吗withCache()?这个怎么样:
function withCache<F extends Function>(key: string, fn: F): F {
// implementation ...
}
Run Code Online (Sandbox Code Playgroud)
没有重载,并且返回值始终与参数的类型相同fn:
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 :)
Run Code Online (Sandbox Code Playgroud)
那对你有用吗?祝你好运!