Skip to content
GreenSnippets

Retry an async operation with exponential backoff

Retry a failing promise-returning function with growing, jittered delays and a retry predicate.

JavaScript
async function retry(fn, { retries = 3, baseDelay = 200, maxDelay = 5000, shouldRetry = () => true } = {}) {
  let attempt = 0;
  for (;;) {
    try {
      return await fn(attempt);
    } catch (error) {
      if (attempt >= retries || !shouldRetry(error)) throw error;
      const exp = Math.min(maxDelay, baseDelay * 2 ** attempt);
      const delay = Math.random() * exp; // "full jitter"
      await new Promise((resolve) => setTimeout(resolve, delay));
      attempt++;
    }
  }
}

How it works

Transient failures (timeouts, 502/503, rate limits) are often fixed by waiting and trying again. Delays double each attempt up to maxDelay, with random jitter so many clients do not retry in lockstep.

Only retry idempotent operations, or ones protected by an idempotency key. Retrying a non-idempotent POST can duplicate side effects. Use shouldRetry to avoid retrying permanent errors such as 400 or 404.