我尝试编写高阶函数包装输入函数并将最近调用的结果缓存为副作用。基本功能(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 :(
现在我知道我可以使这种类型安全 - 直到某一点 - 使用函数重载,如下所示:
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 :)
当我尝试允许带有可选参数的函数(最后一次重载是新的)时出现问题:
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
一样。
有什么方法可以解决这个问题吗?
(作为旁注,有没有办法声明重载,所以我不必重复每个重载的函数类型 < / p>
(...) => R
?)
想出了关于不重复函数类型的第二个问题:只需定义它!
type Function1<T1, R> = (a: T1) => R;
// ...
function withCache<T1, R>(fn: Function1<T1, R>): Function1<T1, R>;
这对异步函数有什么用(假设你想缓存结果而不是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>
}
但是,使用同步函数是不安全的:
//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 :(
有没有办法防范这个?或者编写一个安全地适用于两者的函数?
摘要:@ jcalz的回答是正确的。如果可以假定同步函数,或者可以直接使用Promise而不是它们解决的值,则断言函数类型可能是安全的。但是,如果没有一些unimplemented language improvements,则无法实现上述同步或异步方案。
答案 0 :(得分:1)
通过下拉列表并选择匹配的第一个来选择超载。
检查以下代码,该代码已成功编译:
declare let f: (a: any, b?: any) => void;
declare let g: (a: any) => void;
g = f; // okay
函数f
是一个接受一个或两个参数的函数,而g
被声明为接受一个参数的函数。您可以将值f
分配给变量g
,因为您可以在任何可以调用一个参数的函数的地方调用一个或两个参数的任何函数。事实上,第二个参数是可选,这使得这个赋值工作。
您还可以执行其他任务:
f = g; //okay
因为你可以在任何地方调用一个参数的任何函数,你可以调用一个或两个参数的函数。这意味着这两个类型的函数是可相互分配的(尽管它们不是等价的,这有点不健全)。
如果我们只关注这两个重载:
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
上面对f
和g
的讨论意味着与其中一个重载相匹配的任何内容都会匹配另一个。因此,无论您首先列出哪一个,都会被选中。对不起,你实际上不能同时使用它们。
在这一点上,我可以建议你开始提出一套妥协的过载,这些过载可以提供合理的行为,但让我们备份:
您不想要withCache()
的类型安全版本吗?怎么样:
function withCache<F extends Function>(key: string, fn: F): F {
// implementation ...
}
无重载,返回值始终与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 :)
这对你有用吗?祝你好运!