retry

import { retry } from "jsr:@ende/async/unstable-retry";

function ยท experimental

async function retry<T>(fn: (() => Promise<T>) | (() => T), options?: RetryOptions): Promise<T>

Calls the given (possibly asynchronous) function up to maxAttempts times. Retries as long as the given function throws. If the attempts are exhausted, throws a RetryError with cause set to the inner exception.

fn: (() => Promise<T>) | (() => T)

The function to retry.

options?: RetryOptions

Additional options.

Returns

The promise that resolves with the value returned by the function to retry.

Example: Example configuration 1

import { retry } from "@ende/async/unstable-retry";
const req = async () => {
 // some function that throws sometimes
};

// Below resolves to the first non-error result of `req`
const retryPromise = await retry(req, {
 multiplier: 2,
 maxTimeout: 60000,
 maxAttempts: 5,
 minTimeout: 100,
 jitter: 1,
});

Example: Example configuration 2

import { retry } from "@ende/async/unstable-retry";
const req = async () => {
 // some function that throws sometimes
};

// Make sure we wait at least 1 minute, but at most 2 minutes
const retryPromise = await retry(req, {
 multiplier: 2.34,
 maxTimeout: 80000,
 maxAttempts: 7,
 minTimeout: 1000,
 jitter: 0.5,
});

Example: Only retry on specific error types

import { retry } from "@ende/async/unstable-retry";

class HttpError extends Error {
  status: number;
  constructor(status: number) {
    super(`HTTP ${status}`);
    this.status = status;
  }
}

const req = async () => {
  // some function that throws HttpError
};

// Only retry on 429 (rate limit) or 5xx (server) errors
const retryPromise = await retry(req, {
  isRetriable: (err) =>
    err instanceof HttpError && (err.status === 429 || err.status >= 500),
});

Example: Waiting as long as the server asked

Waiting for the longer of the backoff and the server's instruction keeps the retry from arriving before the server is ready, while the backoff still grows when the server gives no instruction.

import { retry } from "@ende/async/unstable-retry";
import { assertEquals } from "@ende/assert";

class RateLimited extends Error {
  retryAfterMs: number;
  constructor(retryAfterMs: number) {
    super("Too many requests");
    this.retryAfterMs = retryAfterMs;
  }
}

let attempts = 0;
const waits: number[] = [];

const result = await retry(() => {
  attempts++;
  if (attempts === 1) throw new RateLimited(30);
  if (attempts === 2) throw new Error("Service unavailable");
  return "ok";
}, {
  minTimeout: 1,
  jitter: 0,
  getDelay(error, _attempt, computedDelay) {
    const wait = error instanceof RateLimited
      ? Math.max(computedDelay, error.retryAfterMs)
      : computedDelay;
    waits.push(wait);
    return wait;
  },
});

assertEquals(result, "ok");
assertEquals(waits, [30, 2]);

Source