Skip to content

Selective retries

The problem

Not every failure deserves a retry. Network blips and 5xxs are transient; a 400 or 401 will fail the same way every time, so retrying just wastes time.

The solution

shouldRetry is called before each retry with the error. Return false to stop immediately. (When you need to decide based on the result rather than the error, reach for a BailError instead.)

ts
await retry(
  async () => {
    const res = await fetch('/api/things');
    if (!res.ok) throw Object.assign(new Error('Request failed'), { status: res.status });
    return res.json();
  },
  {
    times: 4,
    shouldRetry: (error) => {
      const status = (error as { status?: number }).status;
      return status === undefined || status >= 500; // retry network + 5xx only
    },
  },
);

Observe every retry

onRetry fires on each retry with the error and the resolved options (including retained, the count so far) — perfect for logs and metrics:

ts
await retry(callApi, {
  times: 3,
  onRetry: (error, { retained, times }) => {
    logger.warn(`Retry ${retained}/${times}`, { error });
    metrics.increment('api.retry');
  },
});

Try it

Every retry is logged below with its attempt number — the same information onRetry gives you.

attempt timelineidle
  • failed
  • timed out
  • bailed
  • pending
  • succeeded

onRetry runs once per amber marker; shouldRetry can cut the sequence short.

  1. Press Run to start.