Reference
yieldless/retry
Exponential backoff with jitter and abort-aware delays.
yieldless/retry wraps tuple-returning operations with exponential backoff and abort-aware sleep.
Exports
safeRetry(operation, options): Promise<SafeResult<T, E>>
Options
| Option | Description |
|---|---|
maxAttempts | Total number of attempts including the first |
baseDelayMs | Initial delay before the first retry |
maxDelayMs | Upper bound on the computed delay |
factor | Multiplier applied to the delay after each attempt |
jitter | Jitter strategy applied to the delay |
signal | AbortSignal that stops the retry loop immediately |
shouldRetry(error, attempt) | Predicate that decides whether to retry a given error |
onRetry(state) | Callback invoked before each retry attempt |
Example
import { safeTry } from "yieldless/error";
import { safeRetry } from "yieldless/retry";
const [error, response] = await safeRetry(
async (_attempt, signal) => safeTry(fetchWithSignal(signal)),
{
maxAttempts: 5,
baseDelayMs: 100,
shouldRetry: (error) => error.name !== "ValidationError",
},
);Operational rules
- Attempt counts start at 1
maxAttemptsincludes the first attempt- The retry loop stops immediately when the parent signal is aborted
- Jitter defaults to
"full"to avoid herd behavior
Good retry targets
- HTTP calls to other services
- Transient database connection failures
- Temporary subprocess startup issues
Bad retry targets
- Validation failures
- Permission errors
- Business-rule violations that are deterministic
Good
Retry only the noisy boundary and keep validation outside the loop.
const [inputError, input] = parseSafe(schema, rawInput);
if (inputError) return [inputError, null] as const;
const [error, response] = await safeRetry(
(_attempt, signal) => fetchJsonSafe(urlFor(input), { signal }),
{
maxAttempts: 4,
baseDelayMs: 100,
signal,
shouldRetry: (error) =>
!(error instanceof HttpStatusError) || error.status >= 500,
},
);Use onRetry() for logging, metrics, or tests.
await safeRetry(operation, {
onRetry: ({ attempt, delayMs, error }) => {
logger.warn({ attempt, delayMs, error }, "retrying request");
},
});Avoid
Do not retry an entire request handler when only one transport call is flaky.
await safeRetry(
() => handleWholeRequest(context),
{ maxAttempts: 3 },
);Do not retry deterministic failures.
await safeRetry(
() => parseUserInput(input),
{ maxAttempts: 5 },
);