Yo folks, I’m wiring up a fetch wrapper that retries with exponential backoff + jitter, and I’m trying to keep it fast under load. My failure mode right now is when the API has a brief outage, a bunch of callers all retry around the same time and it spikes even harder.
const sleep = (ms, signal) => new Promise((res, rej) => {
const id = setTimeout(res, ms);
signal?.addEventListener("abort", () => {
clearTimeout(id);
rej(new DOMException("Aborted", "AbortError"));
}, { once: true });
});
export async function fetchWithRetry(url, opts = {}) {
const {
retries = 5,
baseDelay = 200,
maxDelay = 5000,
signal,
} = opts;
let lastErr;
for (let attempt = 0; attempt <= retries; attempt++) {
try {
const res = await fetch(url, { ...opts, signal });
if (res.ok) return res;
if (res.status >= 400 && res.status < 500 && res.status !== 429) return res;
throw new Error(`HTTP ${res.status}`);
} catch (err) {
lastErr = err;
if (attempt === retries) break;
const exp = Math.min(maxDelay, baseDelay * 2 ** attempt);
const jitter = exp * (0.5 + Math.random());
await sleep(jitter, signal);
}
}
throw lastErr;
}
Algorithm-wise, what’s a solid pattern to reduce herd behavior here (like request coalescing, per-host token bucket, or a shared retry scheduler) without making the complexity explode?