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.