Skip to content

withRetry

The withRetry helper retries a promise-returning (or sync) function with exponential backoff until it succeeds, retries are exhausted, or retries are aborted. It is used internally by the Event Bus and Event Observer handler paths, and is available for your own application code.

An in Depth Example

This guide also has an in depth example of a working application built with Nimbus. Combining DDD, CQRS and Event Sourcing.

Check out the In Depth Example page to learn how everything is connected and works out in a real-world application.

Basic Usage

typescript
import { withRetry } from "@nimbus-cqrs/core";

const data = await withRetry(
    async (attempt) => {
        console.log(`Attempt ${attempt}`);

        const response = await fetch("https://api.example.com/resource");

        if (!response.ok) {
            throw new Error(`HTTP ${response.status}`);
        }

        return response.json();
    },
    {
        maxRetries: 3,
        initialDelayMs: 500,
        maxDelayMs: 10_000,
    },
);

On exhaustion, withRetry rethrows the last error. Total attempts equal maxRetries + 1.

Options

OptionTypeDefaultDescription
maxRetriesnumber2Retries after the initial attempt
initialDelayMsnumber1000Base delay before the first retry
maxDelayMsnumber30000Cap for the exponential base delay (before jitter)
factornumber2Exponential growth factor
jitterFactornumber0.1Fraction of the base delay added as random jitter (0 disables)
maxRetryTimeMsnumber-Optional wall-clock budget for the whole retry sequence
signalAbortSignal-Cancel waiting between retries and stop further attempts
shouldRetry(context: RetryContext) => boolean-Return false to stop retrying and rethrow
onRetry(context: RetryContext) => void | Promise-Called before each retry delay

RetryContext includes error, attempt (1-based failed attempt), retriesLeft, and delayMs.

Aborting Retries

From inside the function

Throw RetryAbortedError to stop without consuming further retries:

typescript
import { RetryAbortedError, withRetry } from "@nimbus-cqrs/core";

await withRetry(async () => {
    const response = await fetch(url);
    if (response.status === 404) {
        throw new RetryAbortedError("Resource not found");
    }
    if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
    }
    return response.json();
});

With AbortSignal

typescript
import { withRetry } from "@nimbus-cqrs/core";

const controller = new AbortController();

const promise = withRetry(() => doWork(), {
    maxRetries: 5,
    signal: controller.signal,
});

// Later, e.g. on shutdown:
controller.abort(new Error("Shutting down"));

Filtering Retriable Errors

typescript
import { withRetry } from "@nimbus-cqrs/core";

await withRetry(() => callApi(), {
    maxRetries: 5,
    shouldRetry: ({ error }) => !(error.name === "ValidationError"),
    onRetry: ({ attempt, delayMs, error }) => {
        console.warn(`Retry ${attempt} in ${delayMs}ms`, error.message);
    },
});

calculateBackoffDelay

calculateBackoffDelay is the shared exponential backoff helper used by withRetry and by EventSourcingDB connection reconnects:

typescript
import { calculateBackoffDelay } from "@nimbus-cqrs/core";

const delayMs = calculateBackoffDelay(1000, 0, {
    maxDelayMs: 30_000,
    jitterFactor: 0.1,
});
ParameterTypeDescription
initialDelayMsnumberBase delay before exponential scaling
attemptnumberZero-based retry index (0 = first retry)
optionsobjectOptional maxDelayMs, factor, jitterFactor