, ,

Stop Unnecessary API Calls by Using AbortController the Right Way

Posted by

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

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:

  1. AbortController → a controller object you create
  2. signal → an object passed into fetch() 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:

  1. Create a new controller for each request
  2. Pass signal to fetch()
  3. Call abort() when requests are no longer needed
  4. Handle AbortError gracefully
  5. 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

Your email address will not be published. Required fields are marked *