Learn how to cancel fetch requests, prevent race conditions, and save bandwidth the modern way.

Introduction
Let’s start with a pain every dev knows 👇
You type in a search bar that fetches results on each keystroke.
You type “React”, and it sends 5 requests:R → Re → Rea → Reac → React
By the time the first response returns, it overwrites the latest one, showing wrong results.
That’s not just annoying, it’s wasteful.
The fix? Cancel old requests the moment a new one starts.
And that’s exactly what AbortController
was built for.
In this post, we’ll explore:
✅ How AbortController
works under the hood
✅ How to use it to cancel Fetch requests
✅ How to integrate it into React or async workflows
✅ Common pitfalls and best practices
1️. The Problem: Fetch Doesn’t Cancel on Its Own
fetch()
It’s great, but by default, it can’t be stopped once started.
const res = await fetch("/api/search?q=react");
Even if the user navigates away or types a new query, the request continues to run.
It might return late, overwrite your state, or waste server cycles.
We need a way to tell Fetch:
“Hey, I don’t need this anymore stop now.”
Enter AbortController
.
2️. What Is AbortController?
AbortController
It is a built-in browser API that lets you cancel asynchronous operations, especially. fetch()
.
It works in two parts:
AbortController
→ a controller object you createsignal
→ an object passed intofetch()
that listens for abort events
Example
const controller = new AbortController();
const signal = controller.signal;
fetch("/api/data", { signal })
.then((res) => res.json())
.then(console.log)
.catch((err) => {
if (err.name === "AbortError") {
console.log("Request aborted!");
} else {
console.error(err);
}
});
// Cancel request
controller.abort();
✅ Stops the request instantly
✅ Doesn’t trigger the .then()
chain
✅ Rejects the promise with an AbortError
3️. Real-World Example Search Autocomplete
Imagine you’re building a live search bar:
let controller;
async function handleSearch(query) {
// Cancel the previous request if still pending
if (controller) controller.abort();
controller = new AbortController();
try {
const res = await fetch(`/api/search?q=${query}`, {
signal: controller.signal,
});
const data = await res.json();
console.log("Results:", data);
} catch (err) {
if (err.name === "AbortError") {
console.log("Old request canceled");
} else {
console.error("Fetch error:", err);
}
}
}
Now every time the user types a new letter, old requests are canceled immediately.
✅ Prevents race conditions
✅ Reduces unnecessary API calls
✅ Keeps UI in sync with user input
4️. Using AbortController in React
Let’s make it practical in a React app, say you fetch data inside a useEffect()
.
❌ Without AbortController (bad practice)
useEffect(() => {
fetch(`/api/user/${id}`)
.then((res) => res.json())
.then(setUser);
}, [id]);
If id
When changes quickly (like switching profiles), the previous request may still be resolved, even after the component is unmounted, by updating the state.
✅ With AbortController (the right way)
useEffect(() => {
const controller = new AbortController();
fetch(`/api/user/${id}`, { signal: controller.signal })
.then((res) => res.json())
.then(setUser)
.catch((err) => {
if (err.name !== "AbortError") console.error(err);
});
return () => controller.abort();
}, [id]);
✅ Cancels requests when:
- The component unmounts
- Dependencies change
✅ Prevents memory leaks and unwanted state updates
5️. Chaining AbortControllers for Multiple Requests
You can use one controller for multiple requests, aborting them all at once.
const controller = new AbortController();
Promise.all([
fetch("/api/posts", { signal: controller.signal }),
fetch("/api/comments", { signal: controller.signal }),
])
.then(([posts, comments]) => console.log(posts, comments))
.catch((err) => {
if (err.name === "AbortError") console.log("All requests canceled");
});
// Cancel both
controller.abort();
✅ Ideal for dashboards or pages that load multiple resources simultaneously
6️. Handling Timeouts Gracefully
Combine AbortController
with setTimeout()
For timeouts:
async function fetchWithTimeout(url, ms = 5000) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), ms);
try {
const res = await fetch(url, { signal: controller.signal });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (err) {
if (err.name === "AbortError") throw new Error("Request timed out");
throw err;
} finally {
clearTimeout(timeout);
}
}
fetchWithTimeout("/api/data").catch(console.error);
✅ Automatically cancels long-running requests
✅ Prevents infinite loading states
7️. Common Mistakes with AbortController
⚠️ 1. Forgetting to pass signal
fetch(url); // controller.abort() won’t work
You must pass { signal: controller.signal }
or abort does nothing.
⚠️ 2. Reusing the same controller after aborting
Once aborted, that controller’s signal is done. Create a new one each time.
⚠️ 3. Ignoring the AbortError
When a request is canceled, it throws handle it gracefully instead of showing an error to the user.
8️. Why This Matters
Every API call costs:
- Server CPU cycles
- Network bandwidth
- User time
Canceling unused requests isn’t just optimization, it’s responsible programming.
AbortController
Helps you:
✅ Prevent duplicate requests
✅ Avoid race conditions
✅ Improve app performance
✅ Clean up side effects automatically
Once you start canceling requests, your UI feels snappier and your network tab finally looks clean.
Conclusion
AbortController
is one of those simple-but-powerful APIs most developers overlook, yet it’s the key to making your app fast and efficient.
✅ Key takeaways:
- Create a new controller for each request
- Pass
signal
tofetch()
- Call
abort()
when requests are no longer needed - Handle
AbortError
gracefully - Use it in React, search inputs, and timeout logic
You don’t need to make fewer requests you just need to cancel smarter.
Call to Action
Have you used AbortController
In your projects yet?
Drop a comment 👇 I’d love to see how you’re handling request cancellation in your apps.
And if your teammate’s still letting old fetches pile up in the background…
👉 Share this post before their browser melts.
Leave a Reply