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.