Yieldless
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

OptionDescription
maxAttemptsTotal number of attempts including the first
baseDelayMsInitial delay before the first retry
maxDelayMsUpper bound on the computed delay
factorMultiplier applied to the delay after each attempt
jitterJitter strategy applied to the delay
signalAbortSignal 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
  • maxAttempts includes 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 },
);

On this page