Fetch Patterns
Master retry logic, timeouts, AbortController, and concurrency control for production-grade network handling
Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.
Part of the free JavaScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
🎯 What You'll Learn
💡 Running Code Locally: While this online editor runs real JavaScript, some advanced examples may have limitations. For the best experience:
Why Plain fetch() Is Not Enough
Modern JavaScript apps live and die by network calls. A slow or failed request can ruin the whole UX: loaders that never end, buttons that "do nothing," and users smashing refresh.
The Fetch API is powerful but low-level. On its own, it only sends a request and returns a response. Real production apps need retries , timeouts , and cancellation .
Building a Basic Fetch Wrapper
A good pattern is to wrap fetch in a function that normalizes errors and parses JSON automatically:
Implementing Retry Logic with Backoff
Retries are useful when failure is temporary: user's Wi-Fi hiccups, server has a momentary glitch, DNS blip. But we should NOT blindly retry on every error. Retrying a 404 or 401 doesn't help.
Combining Retry with safeFetch
Refactor so callers always get parsed data with automatic retries:
Adding Timeouts with AbortController
By default, fetch can hang for a long time on slow networks. We want client-side timeouts so requests don't hang forever:
AbortController Basics
The browser gives us AbortController to cancel requests instantly. This is critical for modern apps.
Real-World: Cancel Previous Search Requests
Consider a user rapidly typing into a search bar. Without cancellation, you'll get multiple responses arriving out of order — causing flickering, stale results, and horrible UX.
Linking Multiple Requests to One Cancellation
You can connect multiple requests to one AbortController. Canceling it stops ALL in-flight operations.
Concurrency Control: Limiting Simultaneous Requests
If your app fires too many fetches at once, you get UI lag, browser freezing, and backend spikes. A good system caps how many fetches can run at once:
Exponential Backoff with Jitter
Used by Amazon, Google, Stripe, PayPal. Jitter adds randomness to prevent "thundering herd" problems where many clients retry at the same time.
🚀 Production-Ready: The Complete Pattern
This is the enterprise-grade pattern combining timeout, retry, backoff, and abort:
UI Stability: Preventing Race Conditions
The worst UX mistake in async UIs is overwriting content from old responses. This pattern ensures only the latest request updates the UI:
Error Lifecycle: Mapping Every Failure Type
Professionally built apps classify failures for better UX and analytics:
❌ Common Mistakes to Avoid
These mean bad request, unauthorized, or not found. Retrying wastes time.
Re-posting an order or payment can charge multiple times!
🎯 Key Takeaways
🔥 Practice Challenges
🎉 Lesson Complete!
You now have a complete toolkit of production-grade fetch patterns. Next up: Web Storage APIs.
Practice quiz
Why is plain fetch() not enough for production, per the lesson?
- It can't parse JSON
- It only works in Node.js
- It lacks retries, timeouts, and cancellation on its own
- It always returns text
Answer: It lacks retries, timeouts, and cancellation on its own. Fetch is low-level: it just sends a request and returns a response. Production apps also need retries, timeouts, and cancellation.
In the safeFetch wrapper, what happens when res.ok is false?
- It throws an Error with the status attached
- It returns null
- It retries automatically
- It returns the response unchanged
Answer: It throws an Error with the status attached. safeFetch turns HTTP errors into thrown errors, attaching err.status and err.body so callers handle them in one place.
According to the retry rules, which errors should you NOT retry?
- Network errors
- 5xx server errors
- Timeout errors
- 4xx client errors like 401 and 404
Answer: 4xx client errors like 401 and 404. The rules say retry on network errors and 5xx, but DON'T retry 4xx client errors (401, 404) since retrying won't help.
What does exponential backoff do between retries?
- Keeps the delay constant
- Doubles the wait time each retry (backoffMs *= 2)
- Halves the delay each retry
- Removes the delay entirely
Answer: Doubles the wait time each retry (backoffMs *= 2). Exponential backoff multiplies the delay by 2 each attempt, so each retry waits longer than the last.
Which browser API is used to add a client-side timeout to fetch?
- AbortController
- setInterval
- Promise.allSettled
- navigator.connection
Answer: AbortController. AbortController is used: a setTimeout calls controller.abort(), and controller.signal is passed to fetch to cancel it.
When a fetch is aborted, what is the error's name?
- TimeoutError
- CancelError
- AbortError
- NetworkError
Answer: AbortError. An aborted fetch rejects with an error whose name is 'AbortError', which the lesson checks to handle timeouts/cancellation.
In the cancel-previous-search pattern, why abort the old request before a new one?
- To save the user's password
- To prevent stale, out-of-order results from flickering the UI
- To reduce JSON size
- To enable HTTPS
Answer: To prevent stale, out-of-order results from flickering the UI. Aborting the previous controller keeps the UI synced with the LATEST query only, preventing stale, out-of-order results.
How can you cancel several in-flight requests with one call?
- Call fetch.cancelAll()
- Use a different controller per request
- Reload the page
- Pass the same AbortController signal to all of them and abort it once
Answer: Pass the same AbortController signal to all of them and abort it once. Linking multiple fetches to one controller's signal means a single controller.abort() stops all of them.
What problem does the RequestQueue concurrency control solve?
- Parsing invalid JSON
- API storming (too many simultaneous requests causing lag and backend spikes)
- Expired tokens
- CORS errors
Answer: API storming (too many simultaneous requests causing lag and backend spikes). RequestQueue caps how many fetches run at once (e.g. 4), preventing UI lag, browser freezing, and backend spikes.
In the UI stability pattern, how is a stale response detected and ignored?
- By comparing timestamps
- By checking res.ok
- By checking if the request's id still equals the current activeRequestID
- By aborting the controller
Answer: By checking if the request's id still equals the current activeRequestID. Each request gets an incrementing id; the UI only updates if id === activeRequestID, so older responses are ignored.