@variablesoftware/ts-retry-backoff
Version:
🎛️🔁🚀 A tiny, zero‑dependencies retry helper with exponential backoff + jitter—usable for KV, HTTP, Durable Objects, or any async function.
57 lines (56 loc) • 2.12 kB
JavaScript
// src/index.ts (or wherever your entrypoint is)
export const strategies = {
exponential: (n, base) => base * 2 ** n,
linear: (n, base) => base * (n + 1),
fibonacci: (n, base) => {
let a = 0, b = 1;
for (let i = 0; i < n; i++)
[a, b] = [b, a + b];
return a * base;
}
};
export function delay(ms, signal) {
if (signal?.aborted) {
// DOMException so it matches fetch/stream aborts
throw new DOMException('Aborted', 'AbortError');
}
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
signal?.removeEventListener('abort', onAbort);
resolve();
}, ms);
const onAbort = () => {
clearTimeout(timer);
reject(new DOMException('Aborted', 'AbortError'));
};
signal?.addEventListener('abort', onAbort, { once: true });
});
}
export async function retryBackoff(fn, opts = {}) {
const { maxRetries = 5, baseDelayMs = 50, minDelayMs, maxDelayMs, strategy = strategies.exponential, jitter = 0, retryOn = () => true, onRetry = () => { }, onSuccess = () => { }, onGiveUp = () => { }, signal } = opts;
let attempt = 0;
while (true) {
if (signal?.aborted)
throw new DOMException('Aborted', 'AbortError');
try {
const result = await fn();
onSuccess(result, attempt);
return result;
}
catch (err) {
if (attempt >= maxRetries || !retryOn(err)) {
onGiveUp(err, attempt);
throw err;
}
let rawDelay = strategy(attempt, baseDelayMs);
if (typeof minDelayMs === 'number')
rawDelay = Math.max(rawDelay, minDelayMs);
if (typeof maxDelayMs === 'number')
rawDelay = Math.min(rawDelay, maxDelayMs);
const delayWithJitter = rawDelay + (jitter > 0 ? Math.random() * rawDelay * jitter : 0);
onRetry(err, attempt, delayWithJitter);
await delay(delayWithJitter, signal);
attempt++;
}
}
}