Skip to content

Iterative retry

25 December 2023

All the retry() functions I've found were all recursive, and I had a feeling that it could potentially raise a stack overflow error if the number of attempts was large enough. I validated my concerns via testing with retries of 5000+ returning an error.

typescript
/* retry a function n times, with a delay between */
export async function retryRecursive<T>(
  fn: () => Promise<T> | T,
  attempts: number = 3,
  delay: number = 5000,
): Promise<T> {
  try {
    return await fn();
  } catch (error) {
    if (attempts < 1) {
      throw error;
    }
    console.log("retrying...");
    await sleep(delay);
    return retryRecursive(fn, attempts - 1, delay);
  }
}

/* Sleep for some time */
export async function sleep(ms: number) {
  return new Promise((res) => setTimeout(res, ms));
}

So here's an iterative version I wrote instead that doesn't suffer from stack overflow.

typescript
/* retry a function n times, with a delay between */
export async function retry<T>(
  fn: () => Promise<T> | T,
  attempts: number = 3,
  delay: number = 5000,
) {
  let error;
  for (let i = 0; i < attempts; i++) {
    try {
      return await fn();
    } catch (err) {
      if (i < attempts - 1) {
        /* not on the last attempt yet */
        console.log("retrying...");
        await sleep(delay);
      }
      error = err;
    }
  }
  throw error;
}

/* Sleep for some time */
export async function sleep(ms: number) {
  return new Promise((res) => setTimeout(res, ms));
}

The reason for setTimeout() instead of setInterval() is because "setInterval starts a function every n milliseconds, without any consideration about when a function finishes execution. (source)" It's possible for the function to take longer than the delay, e.g. with a poor network connection, resulting in the delay between attempts being much shorter than expected.

setInterval() Note that the delay between attempts is not 1 second. setInterval

setTimeout() The delay between attempts is 1 second. !setTimeout