retry
Retries an async function up to a specified number of attempts.
1/**
2 * Retries an async function up to a specified number of attempts.
3 *
4 * @template T - Return type of the async function.
5 * @param fn - The async function to retry.
6 * @param retries - Maximum number of attempts (default is 3).
7 * @param delay - Delay in milliseconds between attempts (default is 0).
8 * @returns A promise resolving to the function result, or rejecting if all attempts fail.
9 *
10 * @example
11 * await retry(() => fetchWithRandomFailure(), 5, 1000);
12 */
13export async function retry<T>(
14 fn: () => Promise<T>,
15 retries: number = 3,
16 delay: number = 0
17): Promise<T> {
18 let attempt = 0;
19
20 while (attempt < retries) {
21 try {
22 return await fn();
23 } catch (err) {
24 attempt++;
25 if (attempt >= retries) throw err;
26 if (delay > 0) await new Promise(res => setTimeout(res, delay));
27 }
28 }
29
30 // This should never be reached
31 throw new Error('Retry failed unexpectedly');
32}
Robust Retry Logic
Automatically retries a failing async operation up to a configurable number of attempts, reducing flakiness in unstable environments.
Configurable Delay Between Attempts
Supports delay intervals between retries, which is useful for exponential backoff or avoiding rate limits.
Compact and Readable
Implements retry logic with clear control flow and minimal boilerplate.
Graceful Error Propagation
Throws the last encountered error if all retries fail, preserving error context for downstream handling.
Tests | Examples
1test('retry - resolves on first try', async () => {
2 const fn = jest.fn().mockResolvedValue('ok');
3 const result = await retry(fn, 3);
4 expect(result).toBe('ok');
5 expect(fn).toHaveBeenCalledTimes(1);
6});
7
8test('retry - retries and eventually succeeds', async () => {
9 const fn = jest
10 .fn()
11 .mockRejectedValueOnce(new Error('fail'))
12 .mockResolvedValueOnce('success');
13 const result = await retry(fn, 2);
14 expect(result).toBe('success');
15 expect(fn).toHaveBeenCalledTimes(2);
16});
17
18test('retry - fails after max attempts', async () => {
19 const fn = jest.fn().mockRejectedValue(new Error('always fails'));
20 await expect(retry(fn, 3)).rejects.toThrow('always fails');
21 expect(fn).toHaveBeenCalledTimes(3);
22});
23
24test('retry - waits between retries', async () => {
25 const fn = jest
26 .fn()
27 .mockRejectedValueOnce(new Error('fail'))
28 .mockResolvedValueOnce('ok');
29
30 const now = Date.now();
31 const result = await retry(fn, 2, 100);
32 const elapsed = Date.now() - now;
33
34 expect(result).toBe('ok');
35 expect(elapsed).toBeGreaterThanOrEqual(100);
36});
Common Use Cases
Network Request Resilience
Retry HTTP or API calls that may intermittently fail due to connectivity or server load.
Database or I/O Operations
Stabilize flaky file system or database interactions in unreliable environments.
Third-Party Integration
Handle temporary issues with external services (e.g., rate limits, cold starts, timeouts).
Background Jobs and Workers
Ensure retryable tasks are given multiple chances before being marked as failed.
Testing with Intermittent Conditions
Add resilience to integration tests that depend on unstable or eventual-consistency systems.