我构建了一个高阶函数,如果使用相同的参数调用函数,它会缓存函数的结果。这是使用参数实用程序类型返回具有相同签名的函数,将参数传递给包装函数。
function memoize<FN extends (...args: any) => Promise<any>>(fn: FN) {
const cache: Record<string, Promise<Awaited<ReturnType<FN>>>> = {};
return (...args: Parameters<FN>) => {
const cacheKey = JSON.stringify(args);
if (!(cacheKey in cache)) {
// @ts-ignore
const promise = fn(...args);
cache[cacheKey] = promise;
}
return cache[cacheKey];
};
}
如果没有ts-ignore
打字稿,将会失败Type 'Parameters<FN>' must have a '[Symbol.iterator]()' method that returns an iterator. ts(2488)
。我该如何正确解决这个问题?
该函数的使用方式如下:
const cachedFunction = memoize(async (id: string) => fetch(`https://jsonplaceholder.typicode.com/todos/${id}`))
const resultFromAPI = cachedFunction("1")
const resultFromCache = cachedFunction("1")
正如microsoft/TypeScript#36874中报告的那样,不被视为可传播的部分
Parameters<FN>
被认为是 TypeScript 中的错误。这可以通过将约束从更改为来解决:(...args: any) => ⋯
(...args: any[]) => ⋯
不过,这不是这里推荐的方法。
Awaited
、Parameters
和实用程序类型作为条件类型ReturnType
实现。在 的体内,类型是泛型类型参数,因此和是泛型条件类型。众所周知,TypeScript 无法对通用条件类型进行大量分析。它往往会推迟对它们的评估,因此在内部,类型大多是不透明的。因此,当您调用编译器时,无法确定是否合适。所以它要么一直报告错误,要么不报告错误。memoize()
FN
Parameters<FN>
Awaited<ReturnType<FN>>
memoize()
Parameters<FN>
fn(...args)
args
由于
FN
被限制为(...args: any[]) => ⋯
,调用f(...args)
最终会扩大FN
到它的约束,它接受任何参数,因此无论如何它最终都无法报告错误。所以fn(...args)
被接受了,但是显然有些不好的事情也是如此fn(123)
:如果您确实决定使用这种方法,那么您应该非常小心。尽可能避免通用条件类型。
推荐的方法不是泛型条件类型,而是使您的函数泛型,而不是在
FN
完整函数类型中,而是在其参数列表A
和返回类型中R
:这编译得很干净,编译器实际上能够很好地理解你在做什么,如果你编写,
fn(123)
你会得到预期的错误Playground 代码链接