Yevhen Klymentiev
dark
light
console
darkness
y.klymentiev@gmail.com
Reusable Snippets|Practical utility code for everyday use — custom-built and ready to share

retry

Retries an async function up to a specified number of attempts.

TypeScript
Copied!
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

TypeScript
Copied!
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.

Codebase: Utilities -> Functions -> retry | Yevhen Klymentiev