我构建了一个更高阶的函数,如果使用相同的参数调用该函数,该函数将缓存该函数的结果.这是使用Parameters实用程序类型返回具有相同签名的函数,并将参数传递给包装函数.

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份Typescript ,Type 'Parameters<FN>' must have a '[Symbol.iterator]()' method that returns an iterator. ts(2488)份Typescript 将会失败.我如何正确地解决这个问题?

该函数的用法如下:

const cachedFunction = memoize(async (id: string) => fetch(`https://jsonplaceholder.typicode.com/todos/${id}`))
const resultFromAPI = cachedFunction("1")
const resultFromCache = cachedFunction("1")

推荐答案

Parameters<FN>不被视为可传播的部分被认为是打印脚本中的错误,正如在microsoft/TypeScript#36874中所报告的那样.可以通过将constraint(...args: any) => ⋯更改为(...args: any[]) => ⋯来解决此问题:

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)) {
      const promise = fn(...args); // okay
      cache[cacheKey] = promise;
    }

    return cache[cacheKey];
  };
}

尽管如此,这不是这里推荐的方法.


AwaitedParametersReturnType实用程序类型实现为conditional types.在memoize()的主体内,FN类型是generic类型参数,因此Parameters<FN>Awaited<ReturnType<FN>>generic conditional types.众所周知,TypeScrip不能对泛型条件类型进行太多分析.它倾向于推迟对它们的判断,因此在memoize()内部,类型Parameters<FN>大多是不透明的.因此,当您调用fn(...args)时,编译器不能确定args是否合适.因此,它要么必须一直报告错误,要么永远不报告错误.

由于FN被限制为(...args: any[]) => ⋯,因此调用f(...args)最终会将FN扩展到其约束,该约束完全接受任何参数,因此它最终无论如何都不会报告错误.所以fn(...args)是可以接受的,但像fn(123)这样明显不好的东西也是可以接受的:

const promise = fn(123); // okay?!!!

如果你决定使用这种方法,那么,你应该非常小心. 尽可能避免泛型条件类型.


与泛型条件类型不同,推荐的方法是使函数不是在FN(完整函数类型)中泛型,而是在其参数列表A和返回类型R中泛型:

function memoize<A extends any[], R>(fn: (...args: A) => Promise<R>) {
  const cache: Record<string, Promise<R>> = {};

  return (...args: A) => {
    const cacheKey = JSON.stringify(args);

    if (!(cacheKey in cache)) {
      const promise = fn(...args); // okay
      cache[cacheKey] = promise;
    }

    return cache[cacheKey];
  };
}

这样编译起来很干净,编译器实际上能够很好地理解你在做什么,如果你写fn(123),你会得到预期的错误

const promise = fn(123); // error!
//                 ~~~
// Argument of type '[number]' is not assignable to parameter of type 'A'.    

Playground link to code

Typescript相关问答推荐

类型脚本:类型是通用的,只能索引以供阅读.(2862)"

在TypeScript中使用泛型的灵活返回值类型

如何在第一次加载组件时加载ref数组

在泛型类型中对子代执行递归时出错

将对象属性转换为InstanceType或原样的强类型方法

如何在typescript中设置对象分配时的键类型和值类型

如何在另一个参数类型中获取一个参数字段的类型?

参数属性类型定义的REACT-RUTER-DOM中的加载器属性错误

我可以使用TypeScrip从字符串词典/记录中填充强类型的环境对象吗?

如何在已知基类的任何子类上使用`extends`?

RXJS将对键盘/鼠标点击组合做出react

使用泛型识别对象值类型

将对象的属性转换为正确类型和位置的函数参数

是否可以通过映射类型将函数参数约束为预定义类型?

当数组类型被扩展并提供标准数组时,TypeScrip不会抱怨

Angular NG8001错误.组件只能在一个页面上工作,而不是在她的页面上工作

可以用任意类型实例化,该类型可能与

如何在Vue动态类的上下文中解释错误TS2345?

Typescript循环函数引用

如何通过nx找到所有受影响的项目?