4.0 KiB
exponential
(no submodules)
Purpose
Provides createExponentialBackoff, applyJitter, and clampDelay — the three core building blocks for exponential backoff with optional jitter. Exponential backoff is the industry-standard strategy for retrying failed calls against overloaded or degraded services: each subsequent delay doubles (or grows by configurable base), reducing collision probability under high concurrency far better than constant or linear strategies.
Jitter breaks the synchronization problem that arises when many clients retry at identical intervals, which can cause "thundering herd" effects that worsen rather than relieve overloaded services. This module supports four jitter modes aligned with the AWS/Google SRE recommendations: full, equal, decorrelated, and none.
Features
- Configurable
initialdelay,baseexponent (default 2), andmaxcap - Four jitter modes:
full,equal,decorrelated,none - Pluggable
Rngfor deterministic testing — falls back toMath.random() applyJitterusable independently on any delay valueclampDelayutility for explicit range enforcement- All functions are pure (no side effects beyond rng call)
Usage: Basic Example
import { createExponentialBackoff, withRetry } from "@suaveplan/retry";
const strategy = createExponentialBackoff({ initial: 100, max: 30_000, jitter: "full" });
// attempt 1 → ~0–100ms, attempt 2 → ~0–200ms, etc.
await withRetry(fetchQuote, { maxAttempts: 6, delay: strategy });
Usage: Advanced Example
import { createExponentialBackoff, applyJitter, clampDelay } from "@suaveplan/retry";
// Seeded RNG for deterministic tests
const rng = { nextUint32: () => 2147483648, nextFloat: () => 0.5 };
const strategy = createExponentialBackoff(
{ initial: 50, base: 3, max: 10_000, jitter: "equal" },
rng,
);
// Manual composition: clamp after jitter for extra safety
const bounded = (attempt: number) => clampDelay(strategy(attempt), 50, 5_000);
await withRetry(writeRecord, {
maxAttempts: 8,
delay: bounded,
shouldRetry: (err) => isTransient(err),
signal: AbortSignal.timeout(60_000),
});
API Reference
createExponentialBackoff(opts, rng?)
| Parameter | Type | Description |
|---|---|---|
opts.initial |
number |
Base delay for attempt 1 (ms). |
opts.base |
number? |
Exponent base. Default: 2. |
opts.max |
number? |
Maximum delay cap (ms). |
opts.jitter |
JitterMode? |
Jitter mode. Default: "none". |
rng |
Rng? |
Random source. Falls back to Math.random(). |
Returns: RetryStrategy — (attempt: number) => number.
applyJitter(delay, mode, rng?)
| Parameter | Type | Description |
|---|---|---|
delay |
number |
Base delay to apply jitter to (ms). |
mode |
JitterMode |
One of "full", "equal", "decorrelated", "none". |
rng |
Rng? |
Random source. Falls back to Math.random(). |
Returns: number — jitter-adjusted delay.
clampDelay(delay, min, max)
| Parameter | Type | Description |
|---|---|---|
delay |
number |
Unclamped delay (ms). |
min |
number |
Inclusive lower bound. |
max |
number |
Inclusive upper bound. |
Returns: number — Math.max(min, Math.min(max, delay)).
Implementation Notes
The createExponentialBackoff factory captures all configuration in a closure and calls applyJitter internally if a jitter mode other than "none" is configured. This ensures the correct rng is threaded through without callers needing to pass it on every call. The "decorrelated" jitter mode is a simplified variant that maps delay to [delay, 2*delay] — the classic AWS decorrelated formula uses rand(delay/2, prev*3) but requires external state; this simplified form achieves similar dispersion without statefulness. clampDelay is intentionally a standalone function rather than part of the strategy to keep concerns separated and allow explicit composition.