Retry Utility
Two retry helpers on RetryHelper. One retries when a call throws. The other retries when a call succeeds but the result is not what you want yet.
In one example
import { RetryHelper } from '@venizia/ignis-helpers';
// Retries because the call THREW
const data = await RetryHelper.executeWithRetry({
operation: 'fetch-remote-config',
execution: () => fetchConfig(),
maxAttempts: 5,
});
// Retries because the result is not YET what we want
const order = await RetryHelper.executeWithRetryUntil({
operation: 'wait-for-paid-order',
execution: () => orderRepository.findById({ id: orderId }),
until: result => result?.status === 'PAID',
maxAttempts: 5,
});Works in the browser
Import from @venizia/ignis-helpers/core instead, and every method above is available - the whole class is part of the browser-pure surface, verified by the repo's purity gate rather than by inspection.
import { RetryHelper } from '@venizia/ignis-helpers/core';Which one do I need?
| Method | Retries when | Typical use |
|---|---|---|
RetryHelper.executeWithRetry | execution throws | Flaky network calls, connection setup |
RetryHelper.executeWithRetryUntil | until(result) returns false | Polling until data is fresh or a job is done. Powers the repository retry option. |
Both share the same backoff engine and the same habit: on exhaustion, log one logger.warn and hand back the LAST outcome.
RetryHelper.executeWithRetry
static executeWithRetry: <T>(opts: {
operation: string;
execution: (context: { attempt: number; signal?: AbortSignal }) => ValueOrPromise<T>;
maxAttempts?: number; // default 3
maxTotalMs?: number; // total budget across attempts and sleeps
perAttemptTimeoutMs?: number; // race each attempt against a timeout
backoff?: IRetryBackoffOptions;
shouldRetry?: (context: IRetryContext) => boolean;
onRetry?: (context: IRetryContext & { nextDelayMs: number }) => ValueOrPromise<void>;
signal?: AbortSignal;
logger?: ILogger;
}) => Promise<T>;The rules:
- Every thrown error retries, unless
shouldRetryreturnsfalse- then it rethrows immediately. Use this for permanent errors like a400. - Out of attempts or budget? The LAST error is thrown.
signalaborts between attempts and during sleeps. It is also passed toexecution- a running promise cannot be cancelled from outside, so honor it inside if you can.
RetryHelper.executeWithRetryUntil
static executeWithRetryUntil: <T>(opts: {
operation: string;
execution: (context: { attempt: number; signal?: AbortSignal }) => ValueOrPromise<T>;
until: (result: T) => boolean; // return true to stop: "the result is good"
maxAttempts?: number; // default 3
maxTotalMs?: number; // stop starting NEW attempts after this much time
backoff?: IRetryBackoffOptions;
signal?: AbortSignal;
logger?: ILogger;
}) => Promise<T>;The rules:
- A thrown error is never retried. It rethrows immediately. Only a successful call with a "not yet" result retries.
- Out of attempts or budget? The LAST result is returned as-is. No error.
maxTotalMsnever cuts a running read short. It only stops NEW attempts from starting. Zero or negative just means "no retries" - one call still runs.maxAttemptsbelow1throws before anything runs.- An aborted
signalrejects the call - a cancelled caller does not want a stale result.
Use it for any polling: waiting for a job status to flip, for a downstream service to come up, for a replica to catch up.
Backoff and jitter
Both helpers wait between attempts using IRetryBackoffOptions:
interface IRetryBackoffOptions {
strategy?: 'fixed' | 'linear' | 'exponential' | 'schedule'; // default exponential
initialDelayMs?: number; // default 250
multiplier?: number; // exponential growth factor, default 2
maxDelayMs?: number; // cap before jitter, default 30000
scheduleMs?: readonly number[]; // required for 'schedule'
jitter?: 'none' | 'full' | 'equal'; // default full
}| Strategy | Delay for attempt N |
|---|---|
fixed | initialDelayMs |
linear | initialDelayMs * N |
exponential | initialDelayMs * multiplier ** (N - 1) |
schedule | scheduleMs[N - 1], last entry repeats |
| Jitter | Effect |
|---|---|
none | delay used as-is |
full | random in [0, delay) |
equal | random in [delay/2, delay) |
Prefer named constants? RetryBackoffStrategies.EXPONENTIAL, RetryJitterModes.EQUAL, etc.
NOTE
These defaults (250ms, 30s cap) suit network retries. The repository retry option uses its own tighter defaults (50ms up to 500ms) - see Read Retry.
Other exports
| Export | What it does |
|---|---|
RetryHelper.runWithTimeout({ operation, timeoutMs, execution }) | Races execution against a timeout. Omitted or <= 0 means no timeout. |
RetryHelper.isRetryTimeoutError(error) | true when the error is a timeout from runWithTimeout/executeWithRetry. |
RetryHelper.computeBackoffDelayMs({ attempt, backoff }) | The delay both helpers use, exposed for your own loops. |
See also
- Read Retry - the repository
retryoption built onexecuteWithRetryUntil - Repository Read Retry changelog - what shipped and why
- Utilities Overview
Files: