typescript 68 lines · 10 steps

A retry queue with exponential backoff

Persist failed requests in localStorage and replay them with growing delays until they succeed or exhaust their attempts.

Explained by highlit
1type QueuedRequest = {
2 id: string;
3 url: string;
4 method: string;
5 headers: Record<string, string>;
6 body: string | null;
7 attempts: number;
8 nextRetryAt: number;
9};
10 
11const STORAGE_KEY = "retry_queue";
12const MAX_ATTEMPTS = 5;
13const BASE_DELAY_MS = 1000;
14 
15function load(): QueuedRequest[] {
16 try {
17 return JSON.parse(localStorage.getItem(STORAGE_KEY) ?? "[]");
18 } catch {
19 return [];
20 }
21}
22 
23function save(queue: QueuedRequest[]): void {
24 localStorage.setItem(STORAGE_KEY, JSON.stringify(queue));
25}
26 
27export function enqueue(req: Omit<QueuedRequest, "id" | "attempts" | "nextRetryAt">): void {
28 const queue = load();
29 queue.push({
30 ...req,
31 id: crypto.randomUUID(),
32 attempts: 0,
33 nextRetryAt: Date.now(),
34 });
35 save(queue);
36}
37 
38export async function flush(): Promise<void> {
39 const now = Date.now();
40 const queue = load();
41 const remaining: QueuedRequest[] = [];
42 
43 for (const item of queue) {
44 if (item.nextRetryAt > now) {
45 remaining.push(item);
46 continue;
47 }
48 try {
49 const res = await fetch(item.url, {
50 method: item.method,
51 headers: item.headers,
52 body: item.body,
53 });
54 if (!res.ok) throw new Error(`HTTP ${res.status}`);
55 } catch {
56 const attempts = item.attempts + 1;
57 if (attempts < MAX_ATTEMPTS) {
58 remaining.push({
59 ...item,
60 attempts,
61 nextRetryAt: now + BASE_DELAY_MS * 2 ** attempts,
62 });
63 }
64 }
65 }
66 
67 save(remaining);
68}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Persisting the queue to localStorage lets retries survive page reloads and browser restarts.
  2. 2Exponential backoff spaces out retries so a struggling server isn't hammered by immediate reattempts.
  3. 3Capping attempts prevents a permanently failing request from living in the queue forever.

Related explainers

Share this explainer

Here's the card — post it anywhere.

A retry queue with exponential backoff — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code