Retrying a failed request is common; retrying it well is the trick. Exponential backoff re-runs a failing async function with a delay that grows after each attempt — wait 100ms, then 200ms, then 400ms — so a struggling server gets breathing room instead of a hammering. It's the standard resilience pattern for network calls.
Implement retry(fn, options). Call fn; if it rejects, wait and retry, multiplying the delay by factor each time, up to retries attempts. If it still fails, reject with the last error.
function retry(fn, options) {
// options: { retries = 3, delay = 100, factor = 2 }
// returns a promise that resolves with fn's value, or rejects with the
// last error after retries are exhausted.
}
// Retries up to 3 times, waiting 100ms, 200ms, 400ms between attempts:
await retry(() => fetch('/flaky'));
await retry(fetchData, { retries: 5, delay: 50, factor: 2 });
// waits 50, 100, 200, 400, 800 ms between the 5 retries
retries is the retry count — after the first attempt. So retries: 3 means up to 4 total calls (1 + 3).factor — first wait is delay, then delay * factor, then delay * factor², and so on.fn resolves; return its value.factor of 1 — keeps the delay constant (linear retry); the default 2 doubles it each time.