javascript 42 lines · 8 steps

A resilient fetch wrapper with retries and timeout

How to merge default request options and wrap fetch with retry and abort-based timeout logic.

Explained by highlit
1const DEFAULT_OPTIONS = {
2 method: 'GET',
3 timeout: 5000,
4 retries: 3,
5 headers: {
6 Accept: 'application/json',
7 'Content-Type': 'application/json',
8 },
9 cache: 'no-store',
10};
11 
12function resolveRequestOptions(overrides = {}) {
13 const { headers: overrideHeaders, ...restOverrides } = overrides;
14 
15 return {
16 ...DEFAULT_OPTIONS,
17 ...restOverrides,
18 headers: {
19 ...DEFAULT_OPTIONS.headers,
20 ...overrideHeaders,
21 },
22 };
23}
24 
25export async function apiFetch(url, overrides) {
26 const { timeout, retries, ...fetchOptions } = resolveRequestOptions(overrides);
27 
28 for (let attempt = 0; attempt <= retries; attempt++) {
29 const controller = new AbortController();
30 const timer = setTimeout(() => controller.abort(), timeout);
31 
32 try {
33 const response = await fetch(url, { ...fetchOptions, signal: controller.signal });
34 if (!response.ok && attempt < retries) continue;
35 return response;
36 } catch (err) {
37 if (attempt === retries) throw err;
38 } finally {
39 clearTimeout(timer);
40 }
41 }
42}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Nested config objects need a deep merge so overriding one header doesn't wipe the rest.
  2. 2AbortController plus setTimeout gives fetch the request timeout it lacks natively.
  3. 3A retry loop must distinguish the final attempt so it can surface the real error instead of swallowing it.

Related explainers

Share this explainer

Here's the card — post it anywhere.

A resilient fetch wrapper with retries and timeout — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code