How can we ensure a generic async function with a return type that is also generic in Typescript?

I'm currently working on a function that retries an async function multiple times before rejecting. I want to make sure the typescript typings of the retry function are maintained and also ensure that the passed function is of type PromiseLike.

Creating a retry function with a typed response inferred from the passed function is doable, but enforcing that the passed function is PromiseLike can be challenging. If we wrap the ReturnType<T> in a promise as shown below, we end up returning a promise of a promise of T, Promise<Promise<T>> instead of Promise<T>:

export async function retry<T extends () => ReturnType<T>>(fn: T, retries: number = 1) {
  let attempts = 0;
  let response: ReturnType<T>;
  
  while (attempts < retries) {
    response = fn();
    try {
      console.log(`Evaluating attempt #${attempts} ...`);
      return await response;
    } catch (e) {
      console.log(`Attempt #${attempts} failed.`);
      if (attempts < retries) attempts = attempts + 1;
      else return response;
    }
  }
}

The main challenge I'm facing is how to enforce that the generic constraint is an asynchronous function rather than just any Callable. Using TS utilities like Promise and PromiseLike won't work since the return type will end up being wrapped twice with a promise when dealing with an asynchronous function.

Answer №1

To effectively address this issue, one can utilize the

PromiseLike<Awaited<T>>
method, which ensures that the provided function operates asynchronously. This specific combination is comparable to a constraint known as PromiseLike, particularly when anticipating that T will be a promise.

export async function retry<T extends () => PromiseLike<Awaited<ReturnType<T>>>>(fn: T, retries: number = 1) {
  let attempts = 0;
  let response: PromiseLike<Awaited<ReturnType<T>>>;
  
  while (attempts < retries) {
    response = fn();
    try {
      console.log(`Evaluating attempt #${attempts} ...`);
      return await response;
    } catch (e) {
      console.log(`Attempt #${attempts} failed.`);
      if (attempts < retries) attempts = attempts + 1;
      else return response;
    }
  }

  throw Error("Invalid number of retries");
}

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

What is the way to assign a variable to ngClass in Angular?

I'm currently working on creating modals that will display different content based on which button is clicked. Each button should trigger a unique modal to appear, each with its own specific content inside. When a button is clicked, the 'active&a ...

Exploring the concept of 'Abstract classes' within the Svelte framework

As someone who is relatively new to Svelte and frontend development (with primary experience in Rust/C++/Python), I hope you can forgive me for asking what might seem like a basic question. My goal is to showcase different kinds of time-indexed data, with ...

arrange items based on their category into arrays

I have a JSON file that needs to be arranged based on three specific fields. Here is an example snippet of the JSON data: { "Racename": "10KM", "Category": 34, "Gender": "Male", "Work": "Google", "FullName": "Dave Happner", "Rank": 1, "Poni ...

Angular is patiently waiting for the $http request to finish before moving on to the next function

After extensive searching for a solution to my issue, I have come up empty handed. Allow me to provide some context: I am currently adding new features to a Web application that I created, which is used to manage the development of webpages for various cl ...

What is the best way to invoke a function only once in typescript?

Struggling to implement TypeScript in React Native for fetching an API on screen load? I've been facing a tough time with it, especially when trying to call the function only once without using timeouts. Here's my current approach, but it's ...

Having difficulty in utilizing localStorage to update the state

I've attempted to log back in using the stored credentials, however it's not working despite trying everything. The dispatch function is functioning properly with the form, but not when accessing localStorage. App.tsx : useEffect(() => { ...

Filter array of objects by optional properties using TypeGuards

In my TypeScript code, I have defined the following interfaces: interface ComplexRating { ratingAttribute1?: number; ratingAttribute2?: number; ratingAttribute3?: number; ratingAttribute4?: number; } export interface Review { rating: ComplexRati ...

Tips for properly implementing an enum in TypeScript when using the React useState hook

What's the correct way to utilize my useState hook? I have this enum type: export enum Status { PENDING = 'pending', SUCCESS = 'success', ERROR = 'error', } And the useState hook: const [isValid, setIsValid] = use ...

Leveraging jest for handling glob imports in files

My setup involves using webpack along with the webpack-import-glob-loader to import files utilizing a glob pattern. In one of my files (src/store/resources/module.ts), I have the following line: import '../../modules/resources/providers/**/*.resource. ...

Exploring an array using bluebird promises

I am currently facing an issue where I need to iterate over an array containing promises. My goal is to multiply all the values in the array by 2 and then return the updated array: var Bluebird = Promise.noConflict(); var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9 ...

Authenticating to Google APIs using Node.js within a lambda function: A step-by-step guide

I'm encountering an issue when trying to connect a Google sheet to an AWS Lambda function. While the code runs smoothly during local testing, upon deployment to the function, I receive an error message indicating that the credentials.json file cannot ...

Encountering issues following the integration of @angular/flex-layout into an Angular project

After careful consideration, I opted to utilize the responsive grid system provided by @angular/flex-layout instead of Bootstrap. By simply installing the npm package and adding it to my AppModule, I was able to integrate it seamlessly: import { NgModule ...

Triggering event within the componentDidUpdate lifecycle method

Here is the code snippet that I am working with: handleValidate = (value: string, e: React.ChangeEvent<HTMLTextAreaElement>) => { const { onValueChange } = this.props; const errorMessage = this.validateJsonSchema(value); if (errorMessage == null ...

Setting up a React state with an Object using Typescript: A step-by-step guide

As someone who is new to TypeScript, I have a solid understanding of how to set up an interface for certain types. However, I am struggling to comprehend how the same concept would apply to an Object type. interface MyProps { defaultgreeting: 'He ...

Adjusting an item according to a specified pathway

I am currently working on dynamically modifying an object based on a given path, but I am encountering some difficulties in the process. I have managed to create a method that retrieves values at a specified path, and now I need to update values at that pa ...

Manipulating objects from an HTTP Observable while iterating through an Array of objects

I am currently working on processing each element of an array by making a separate HTTP call for each element. I need to track the status of each call and update the UI once all calls are completed. The code snippet below demonstrates my current approach: ...

Angular and Jest combo has encountered an issue resolving all parameters for the AppComponent. What could be causing this error?

I am currently working within a Typescript Monorepo and I wish to integrate an Angular 8 frontend along with Jest testing into the Monorepo. However, I am facing some challenges. The tools I am using are: Angular CLI: 8.3.5 My Approach I plan to use ...

The exportAs attribute is not specified as "ngForm" for any directive

Encountered an error while attempting to test the LoginComponent PhantomJS 2.1.1 (Linux 0.0.0): Executed 3 of 55 (1 FAILED) (0 secs / 0.307 secs) PhantomJS 2.1.1 (Linux 0.0.0) LoginComponent should create FAILED Failed: Uncaught (in promise): Error: Templ ...

Does a typescript definition file exist for Apple MapKit JS?

Before embarking on creating one, I'm curious if anyone has come across a typescript definition file (.d.ts) for Apple MapKit JS? ...

Can ng-content be utilized within the app-root component?

I have successfully developed an Angular application, and now I am looking to integrate it with a content management system that generates static pages. In order to achieve this, I need to utilize content projection from the main index.html file. The desi ...