Fetch Timeouts and Retries: Make Read Requests Predictable
Practical Web Development — Lesson 6 of 10 · Intermediate
A loading spinner needs an exit path. A network request can stall, fail before a response arrives or return an HTTP error. This tutorial adds a per-attempt timeout and at most one retry for a read-only JSON request. It deliberately does not retry writes such as enrolment payments or progress changes.
Build a bounded read helper
Put this function in a browser module. Call it with a public same-origin JSON endpoint. A local test server with a delayed endpoint is useful for exercising timeout behaviour.
async function readJson(url, timeoutMs = 5000) {
for (let attempt = 0; attempt < 2; attempt++) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
let retry = false;
try {
const response = await fetch(url, { signal: controller.signal });
if (!response.ok) {
const error = new Error('HTTP ' + response.status);
error.retryable = [502, 503, 504].includes(response.status);
throw error;
}
return await response.json();
} catch (error) {
retry = attempt === 0 &&
(error.name === 'AbortError' || error instanceof TypeError || error.retryable === true);
if (!retry) throw error;
} finally {
clearTimeout(timer);
}
await new Promise(resolve => setTimeout(resolve, 500));
}
}Read the limits before reusing it
The helper makes at most two attempts. Each includes a timeout and the first failure can add a 500ms delay. It retries selected temporary server errors, an abort caused by its timer, or a fetch TypeError. A TypeError can also reflect a persistent configuration problem such as CORS; a retry does not repair that problem. Inspect the browser network panel when the second attempt fails.
Invalid JSON is not retried here. A 404 is also not retried, because repeating a request does not usually create the missing resource. The helper does not implement Retry-After, external user cancellation or randomized backoff. A production client should respect server retry guidance and avoid synchronized retries from many users.
Connect it to honest UI states
Use distinct idle, loading, success and failure states. Clear loading in a finally block. Show a useful Retry button after failure and retain the previous usable content where appropriate. Do not convert every error into an empty list: “no courses” and “could not load courses” mean different things.
Test observable outcomes
- A normal 200 JSON response should resolve once.
- A 404 should fail after one attempt.
- A 503 followed by success should make two attempts.
- A permanently delayed response should stop after the second timeout.
- A 200 response containing malformed JSON should fail without an automatic retry.
Practice
Add request timing to your development logs without recording private query values. Then introduce an explicit caller cancellation signal and ensure cancellation never triggers a retry. Document the total time users can wait before choosing your timeout values.
Continue learning
Review our semantic HTML project, JavaScript course filter and course platform architecture.
Reference: Technical documentation for this lesson. Examples are learning exercises; test them in your environment.
Comments
Post a Comment