Learning how to effectively incorporate the spread operator with TypeScript's utility type `Parameters` is a valuable skill to

I have implemented a higher order function that caches the result of a function when it is called with the same parameters. This functionality makes use of the Parameters utility type to create a function with identical signature that passes arguments to the underlying function.

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];
  };
}

In absence of the ts-ignore, TypeScript throws an error stating

Type 'Parameters<FN>' must have a '[Symbol.iterator]()' method that returns an iterator. ts(2488)
. What is the correct way to address this issue?

The usage of this function is as follows:

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

Answer №1

The issue arises when Parameters<FN> is not recognized as spreadable in TypeScript, which has been highlighted as a bug and reported on microsoft/TypeScript#36874. To address this, the workaround involves altering the constraint from (...args: any) => ⋯ to (...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];
  };
}

However, it's important to note that this approach is not recommended.


The utility types Awaited, Parameters, and ReturnType are implemented using conditional types. Within the scope of memoize(), the type FN serves as a generic parameter, making Parameters<FN> and

Awaited<ReturnType<FN>>
generic conditional types. TypeScript struggles with analyzing such types effectively, often deferring their evaluation. Consequently, Parameters<FN> remains opaque within memoize(), leading to ambiguity in checking arguments passed to fn(...args).

By restricting FN to (...args: any[]) => ⋯, invoking f(...args) widens FN to its constraint, allowing acceptance of any argument despite potential errors, like fn(123):

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

Cautious consideration must be taken if opting for this method due to the pitfalls associated with generic conditional types.


The preferred alternative to generic conditional types involves making the function generic based on its argument list A and return type R, rather than the complete function type FN:

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];
  };
}

This methodology results in clean compilation, enabling the compiler to accurately interpret situations. For instance, attempting to execute fn(123) would yield an expected error:

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

Explore the Playground link to code

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Problems arising from the layout of the PrimeNG DataView component when used alongside Prime

I've been working with a PrimeNG DataView component that requires the use of PrimeFlex's flex grid CSS classes to set up the grid structure. One of their examples includes the following instructions: When in grid mode, the ng-template element ...

Guide to verifying a value within a JSON object in Ionic 2

Is there a way to check the value of "no_cover" in thumbnail[0] and replace it with asset/sss.jpg in order to display on the listpage? I have attempted to include <img src="{{item.LINKS.thumbnail[0]}}"> in Listpage.html, but it only shows the thumbna ...

Show JSON information in an angular-data-table

I am trying to showcase the following JSON dataset within an angular-data-table {"_links":{"self":[{"href":"http://uni/api/v1/cycle1"},{"href":"http://uni/api/v1/cycle2"},{"href":"http://uni/api/v1/cycle3"}]}} This is what I have written so far in my cod ...

What is the best way to elucidate this concept within the realm of TypeScript?

While diving into my ts learning journey, I came across this interesting code snippet: export const Field:<T> (x:T) => T; I'm having trouble wrapping my head around it. It resembles the function definition below: type myFunction<T> = ...

Incorporate matter-js into your TypeScript project

Upon discovering this file: https://www.npmjs.com/package/@types/matter-js I ran the following line of code: npm install --save @types/matter-js When I tried to use it in the main ts file, an error message appeared: 'Matter' refers to a U ...

Dealing with the "this" problem in TypeScript and its impact on scope

Here is my code snippet: class MyClass { name = "MyClass"; // traditional method definition getName1(){ return this.name; } // method defined as an arrow function getName2 = () => { return this.name; ...

Is there a way to organize items in an array alphabetically according to a predetermined key value?

I have an array of objects containing countries with various values for each country. My goal is to list them alphabetically. // globalBrands { [ { id: 1, title: 'Argentina', content: [{url: 'w ...

Angular's import and export functions are essential features that allow modules

Currently, I am working on a complex Angular project that consists of multiple components. One specific scenario involves an exported `const` object in a .ts file which is then imported into two separate components. export const topology = { "topolo ...

A more efficient method for defining and utilizing string enums in Typescript

enum PAGES { HOME = 'HOME', CONTACT = 'CONTACT', } export const links: { [key: string]: string } = { [PAGES.HOME]: '/home', [PAGES.CONTACT]: '/contact', }; export function getLink(page: string) { return B ...

Access an Angular 2 component through an email hyperlink including querystring parameters

I need to create a deep link with query string parameters for a component, so that when the link is clicked, it opens up the component in the browser. For example: exmaple.com/MyComponent?Id=10 I want to include a link in an email that will open the com ...

The orderBy filter seems to be malfunctioning

I'm utilizing ngx-pipes from this webpage: https://www.npmjs.com/package/ngx-pipes#orderby Specifically, I am using the orderBy pipe. However, when I implement the orderBy pipe in my HTML, the information is not being ordered correctly (from minor t ...

TypeScript's reliance on dependent types

Is it possible to implement this functionality using TypeScript? There exist objects that define various "actions". export interface IAction { action: string; data: any; otherParam1: number; otherParam2: number; } The 'action' p ...

Tips for resolving the error of encountering a forbidden and unexpected token in JSON at position 0 while using React hooks

const [forecastData, setForecastData] = useState({ forecast: []}); useEffect(() => { let ignore = false; const FETCHDATA = async () => { await fetch(forecast,{ headers : { ...

The attribute 'X' is not found in the type 'HTMLAttributes<HTMLDivElement>'.ts(2322)

Encountered an issue using JSX sample code in TSX, resulting in the following error: (property) use:sortable: true Type '{ children: any; "use:sortable": true; class: string; classList: { "opacity-25": boolean; "transition-tr ...

Error in Angular: Http Provider Not Found

NPM Version: 8.1.4 Encountered Issue: Error: Uncaught (in promise): Error: Error in ./SignupComponent class SignupComponent_Host - inline template:0:0 caused by: No provider for Http! Error: No provider for Http! The error message usually indicates the a ...

Encountering a Problem with vue-check-view Library in Typescript

After successfully declaring the js plugin with *.d.ts, I encountered an issue where my view was blank after using .use(checkView). Does the library vue-check-view support Typescript? Error: Uncaught TypeError: Cannot read property '$isServer' o ...

Anticipate that the typescript tsc will generate an error, yet no error was encountered

While working in the IDE to edit the TypeScript code, an issue was noticed in checkApp.ts with the following warning: Argument type { someWrongParams: any } is not assignable to parameter type AddAppToListParams. Surprisingly, when running tsc, no error ...

Unable to utilize the Object.values method with an object in TypeScript

I am attempting to create an array of values from all the keys within an object by using the Object.values(obj) function, but I encountered the following error message: No overload matches this call. Overload 1 of 2, '(o: { [s: string]: string; } | ...

Navigating with Gatsby Link and leveraging TypeScript

I am currently utilizing Gatsby Link with TypeScript and I am looking to pass parameters to a linked component from the source component. The documentation provided by Gatsby states the following: Sometimes you’ll want to pass data from the source pag ...

Having trouble integrating NEXT AUTH with Firebase due to an error: "Cannot import statement outside

Let's take a look at our firebase configuration file: import { getFirestore } from "firebase/firestore"; export const firebaseConfig = { apiKey: process.env.FIREBASE_API_KEY, authDomain: process.env.FIREBASE_AUTH_DOMAIN, projectId: pr ...