retryAsyncWithBackoff
Retries an async function with exponential backoff
1/**
2 * Retries an async function with exponential backoff.
3 *
4 * @param fn - The async function to retry.
5 * @param retries - Max number of retries (default: 3).
6 * @param initialDelay - Delay in ms before first retry (default: 100).
7 * @param backoffFactor - Factor to multiply delay (default: 2).
8 * @returns The resolved value if successful, otherwise throws after final attempt.
9 */
10export async function retryAsyncWithBackoff<T>(
11 fn: () => Promise<T>,
12 retries: number = 3,
13 initialDelay: number = 100,
14 backoffFactor: number = 2
15): Promise<T> {
16 let attempt = 0;
17 let delay = initialDelay;
18
19 while (true) {
20 try {
21 return await fn();
22 } catch (error) {
23 if (attempt >= retries) {
24 throw error;
25 }
26 await new Promise(res => setTimeout(res, delay));
27 delay *= backoffFactor;
28 attempt++;
29 }
30 }
31}
Exponential Backoff Strategy
Increases delay between retries exponentially, helping to avoid overwhelming services during temporary failures.
Customizable Parameters
Allows fine-tuning of retry count, initial delay, and backoff factor to suit different use cases and failure profiles.
Efficient and Lightweight
Minimal implementation without external dependencies, ideal for use in performance-sensitive environments.
Robust Failure Handling
Retries only on actual failures and throws the last error if all attempts fail, preserving error context.
Promise-Based Design
Naturally integrates with
async/await
patterns and error boundaries in modern codebases.
Tests | Examples
1test('resolves on first try', async () => {
2 const fn = jest.fn().mockResolvedValue('ok');
3 const result = await retryAsyncWithBackoff(fn);
4 expect(result).toBe('ok');
5 expect(fn).toHaveBeenCalledTimes(1);
6});
7
8test('resolves after retries', async () => {
9 let count = 0;
10 const fn = jest.fn().mockImplementation(() => {
11 if (count++ < 2) return Promise.reject('fail');
12 return Promise.resolve('done');
13 });
14
15 const result = await retryAsyncWithBackoff(fn, 3, 10);
16 expect(result).toBe('done');
17 expect(fn).toHaveBeenCalledTimes(3);
18});
19
20test('throws after max retries', async () => {
21 const fn = jest.fn().mockRejectedValue(new Error('fail'));
22 await expect(
23 retryAsyncWithBackoff(fn, 2, 10)
24 ).rejects.toThrow('fail');
25 expect(fn).toHaveBeenCalledTimes(3); // initial + 2 retries
26});
Common Use Cases
Resilient API Requests
Retry failing network calls (e.g., REST, GraphQL, WebSockets) with increasing delays to handle rate limits or flakiness.
Background Job Execution
Stabilize async tasks like sending emails, uploading files, or processing messages that may occasionally fail.
Startup and Initialization Logic
Retry bootstrapping code (e.g., service discovery, DB connection) that must succeed eventually.
Polling and Watchers
Gracefully handle retryable fetch or sync cycles in polling loops or data watchers.
Third-Party Integration
Add resilience around external APIs, payment gateways, or infrastructure services prone to temporary instability.