A resilient fetch wrapper retries a failing async call a few times, pausing between attempts, and abandons any single attempt that hangs past a timeout — so a transient network blip recovers on its own instead of surfacing as an error. It sits between your app code and a flaky network: one unreliable fn goes in, a steadier version comes out.
You implement resilientFetchWrapper(fn, options). It returns a new async function that forwards its arguments to fn. If fn rejects, it retries up to retries more times, waiting backoff(attempt) milliseconds before each retry. When a timeout is given, every attempt races fn against a timer, and a timed-out attempt counts as a failure that can be retried like any other.
function resilientFetchWrapper<A extends any[], R>(
fn: (...args: A) => Promise<R>,
options?: {
retries?: number; // extra attempts after the first (default 3)
backoff?: (attempt: number) => number; // ms to wait before retry N (default attempt * 10)
timeout?: number; // per-attempt deadline in ms (optional)
},
): (...args: A) => Promise<R>;
// Flaky endpoint: retry up to 3 times, waiting 10ms, 20ms, 30ms between tries.
const get = resilientFetchWrapper((url) => fetch(url).then((r) => r.json()));
const data = await get('/api/user'); // resolves as soon as one attempt succeeds
// Give each attempt a 50ms deadline; a hung attempt times out and is retried.
const call = resilientFetchWrapper(loadProfile, {
retries: 2,
backoff: (attempt) => attempt * 100, // wait 100ms, then 200ms
timeout: 50,
});
await call(userId); // rejects with the LAST error if all 3 attempts fail
retries is the retry count — the number of extra tries after the first call, so retries: 3 allows up to 4 attempts total (1 initial plus 3 retries).backoff is a function of the attempt number — it receives the 1-based number of the attempt that just failed and returns the milliseconds to wait before the next one. The default (attempt) => attempt * 10 grows the wait linearly.fn.timeout is per attempt, not total — each attempt gets its own fresh deadline; a timeout is just a failure, and a failure can be retried.