AbortController
APIThis tutorial will offer a complete guide on how to use the AbortController
and AbortSignal
APIs in both your backend and frontend. In our case, we’ll focus on Node.js and React.
AbortController
APIThe AbortController
API became part of Node in v15.0.0. It is a handy API for aborting some asynchronous processes, similar to the AbortController
interface in the browser environment.
You need to create an instance of the AbortController
class to use it:
const controller = new AbortController();
An instance of the AbortController
class exposes the abort
method and the signal
property.
Invoking the abort
method emits the abort
event to notify the abortable API watching the controller about the cancellation. You can pass an optional reason for aborting to the abort
method. If you don’t include a reason for the cancellation, it defaults to the AbortError
.
To listen for the abort
event, you need to add an event listener to the controller’s signal
property using the addEventListener
method so that you run some code in response to the abort
event. An equivalent method for removing the event listener is the removeEventListener
method.
The code below shows how to add and remove the abort
event listener with the addEventListener
and removeEventListener
methods:
const controller = new AbortController(); const { signal } = controller; const abortEventListener = (event) => { console.log(signal.aborted); // true console.log(signal.reason); // Hello World }; signal.addEventListener("abort", abortEventListener); controller.abort("Hello World"); signal.removeEventListener("abort", abortEventListener);
The controller’s signal
has a reason
property, which is the reason you pass to the abort
method at cancellation. Its initial value is undefined
. The value of the reason
property changes to the reason you pass as an argument to the abort
method or defaults to AbortError
if you abort without providing a reason for the cancellation. Similarly, the signal’s aborted
property with an initial value of false
changes to true
after aborting.
Unlike in the above example, practical use of the AbortController
API involves passing the signal
property to any cancelable asynchronous API. You can pass the same signal
property to as many cancelable APIs. The APIs will then wait for the controller’s “signal” to abort the asynchronous operation when you invoke the abort
method.
Most of the built-in cancellation-aware APIs implement the cancellation out of the box for you. You pass in the controller’s signal
property to the API, and it aborts the process when you invoke the controller’s abort
method.
However, to implement a custom cancelable promise-based functionality, you need to add an event listener which listens for the abort
event and cancels the process from the event handler when the event is triggered.
Editor’s note: This article was updated by Chizaram Ken in March 2025 to include more comprehensive information on frontend and backend use cases for AbortController
in both Node.js and React.
AbortController
?JavaScript is a single-threaded programming language. Depending on the runtime environment, the JavaScript engine offloads asynchronous processes, such as making network requests, file system access, and other time-consuming jobs, to some APIs to achieve asynchrony.
Ordinarily, we expect the result of an asynchronous operation to succeed or fail. However, the process can also take more time than anticipated, or you may no longer need the results when you receive them.
Therefore, it is logical to terminate an asynchronous operation that has taken more time than it should or whose result you don’t need. However, doing so natively was a daunting challenge for a very long time.
AbortController
was introduced in Node v15.0.0 to abort certain asynchronous operations natively in Node.
AbortController
in Node.jsThe AbortController
API is a relatively new addition to Node. Therefore, a few asynchronous APIs support it at the moment. These APIs include the new Fetch API, timers, fs.readFile
, fs.writeFile
, http.request
, and https.request
.
We will learn how to use the AbortController API with some of the mentioned APIs. Because the APIs work with AbortController
in a similar way, we’ll only look at the Fetch and fs.readFile
API.
AbortController
with the Fetch APIHistorically, node-fetch
has been the de facto HTTP client for Node. With the introduction of the Fetch API in Node.js, however, that is about to change. Fetch is one of the native APIs whose behavior you can control with the AbortController
API.
As explained above, you pass the signal
property of the AbortController
instance to any abortable, promise-based API like Fetch. The example below illustrates how you can use it with the AbortController
API:
const url = "https://jsonplaceholder.typicode.com/todos/1"; const controller = new AbortController(); const signal = controller.signal; const fetchTodo = async () => { try { const response = await fetch(url, { signal }); const todo = await response.json(); console.log(todo); } catch (error) { if (error.name === "AbortError") { console.log("Operation timed out"); } else { console.error(err); } } }; fetchTodo(); controller.abort();
The trivial example above illustrates how to use the AbortController
API with the Fetch API in Node. However, in a real-world project, you don’t start an asynchronous operation and abort it immediately like in the code above.
It is also worth emphasizing that fetch
is still an experimental feature in Node. Its features might change in future versions.
AbortController
with fs.readFile
In the previous section, we looked at using AbortController
with the Fetch API. Similarly, you can use this API with the other cancelable APIs.
You can do this by passing the controller’s signal
property to the API’s respective function. The code below shows how to use AbortController
with fs.readFile
:
const fs = require("node:fs"); const controller = new AbortController(); const { signal } = controller; fs.readFile("data.txt", { signal, encoding: "utf8" }, (error, data) => { if (error) { if (error.name === "AbortError") { console.log("Read file process aborted"); } else { console.error(error); } return; } console.log(data); }); controller.abort();
Since the other cancelable APIs work similarly with AbortController
, we won’t cover them here.
AbortSignal
Each AbortController
class instance has a corresponding AbortSignal
class instance, accessible using the signal
property. However, AbortSignal
has functions such as the AbortSignal.timeout
static method that you can also use independent of AbortController
.
The AbortSignal
class extends the EventTarget
class and can receive the abort
event. Therefore, you can use the addEventListener
and removeEventListener
methods to add and remove listeners for the abort
event:
const controller = new AbortController(); const { signal } = controller; signal.addEventListener( "abort", () => { console.log("First event handler"); }, { once: true } ); signal.addEventListener( "abort", () => { console.log("Second event handler"); }, { once: true } ); controller.abort();
As in the above example, you can add as many event handlers as possible. Invoking the controller’s abort
method will trigger all the event listeners. Removing the abort
event listener after aborting the asynchronous process is standard practice to prevent memory leaks.
You can pass the optional third argument { once: true }
to addEventListener
as we did above instead of using removeEventListener
to remove the event listener. The optional third argument will ensure Node triggers the event listener once and removes it.
AbortSignal
to time out async operationsAs mentioned above, in addition to using it with AbortController
, the AbortSignal
class has some handy methods you might need. One of these methods is the AbortSignal.timeout
static method. As its name suggests, you can use it to abort cancelable asynchronous processes on timeout.
It takes the number of milliseconds as an argument and returns a signal you can use to timeout an abortable operation. The code below shows how you can implement it with the Fetch API:
const signal = AbortSignal.timeout(200); const url = "https://jsonplaceholder.typicode.com/todos/1"; const fetchTodo = async () => { try { const response = await fetch(url, { signal }); const todo = await response.json(); console.log(todo); } catch (error) { if (error.name === "AbortError") { console.log("Operation timed out"); } else { console.error(err); } } }; fetchTodo();
You can use AbortSignal.timeout
similarly with the other abortable APIs.
AbortSignal
to cancel multiple operationsWhen you are working with more than one abort signal, you can combine them using the AbortSignal.any()
. This is really helpful when you need multiple ways to cancel the same operation.
In the example below, I will create two controllers,. The first one will be controlled by the user of the API, and the other will be used for internal timeout purposes. If either one aborts, the event listener gets removed:
// Create two separate controllers for different concerns const userController = new AbortController(); const timeoutController = new AbortController(); // Set up a timeout that will abort after 5 seconds setTimeout(() => timeoutController.abort(), 5000); // Register an event listener that can be aborted by either signal document.addEventListener('click', handleUserClick, { signal: AbortSignal.any([userController.signal, timeoutController.signal]) });
If any signal in the group aborts, the combined signal immediately aborts, ignoring any subsequent abort events. This provides you with a clean separation of concerns.
AbortSignals
AbortSignals
allows you to easily stop a stream. For instance, if your intentions are to stop a stream because you got the value you are looking for, or you want to do something else entirely, you can just use AbortSignal
this way:
const abortController = new AbortController(); const { signal } = abortController; const uploadStream = new WritableStream({ /* implementation */ }, { signal }); // To abort: abortController.abort();
In the example above, the AbortController
creates a signal that, when passed to a WritableStream
constructor options, allows you to cancel the stream’s processing by calling abort()
on the controller.
AbortController
and AbortSignal
As highlighted in the previous section, several built-in asynchronous APIs support the AbortController
API. However, you can also implement a custom abortable promise-based API that uses AbortController
.
Like the built-in APIs, your API should take the signal
property of an AbortController
class instance as an argument as in the example below. It is standard practice for all APIs capable of using the AbortController
API:
const myAbortableApi = (options = {}) => { const { signal } = options; if (signal?.aborted === true) { throw new Error(signal.reason); } const abortEventListener = () => { // Abort API from here }; if (signal) { signal.addEventListener("abort", abortEventListener, { once: true }); } try { // Run some asynchronous code if (signal?.aborted === true) { throw new Error(signal.reason); } // Run more asynchronous code } finally { if (signal) { signal.removeEventListener("abort", abortEventListener); } } };
In the example above, we first checked whether the value of signal’s aborted
property is true
. If so, it means the controller’s abort
method has been invoked. Therefore, we throw an error.
Like mentioned in the previous sections, you can register the abort
event listener using the addEventListener
method. To prevent memory leaks, we are passing the { once: true }
option as the third argument to the addEventListener
method. It removes the event handler after handling the abort
event.
Similarly, we removed the event listener using the removeEventListener
in the finally
block to prevent memory leaks. If you don’t remove it, and the myAbortableApi
function runs successfully without aborting, the event listener you added will still be attached to the signal
even after exiting the function.
The AbortController
API is particularly useful to React developers, but in different ways.
When you want to use event listeners, you will need to add an addEventListener
, and then carefully remove each one with removeEventListener
in a cleanup function.
Although this works, it’s a bit tiring and prone to typographic error. Let us look into a real example that might look familiar.
For instance, you are building a dashboard that tracks mouse movements, listens for keyboard shortcuts, monitors scroll position, and responds to window resizing. Those are four different event listeners to manage.
In a normal scenario, you’d do this:
useEffect(() => { // Define all your handler functions const handleMouseMove = (e) => { /* update state */ }; const handleKeyPress = (e) => { /* update state */ }; const handleScroll = () => { /* update state */ }; const handleResize = () => { /* update state */ }; // Add all the listeners document.addEventListener('mousemove', handleMouseMove); document.addEventListener('keydown', handleKeyPress); window.addEventListener('scroll', handleScroll); window.addEventListener('resize', handleResize); // Return a cleanup function that removes them all return () => { document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('keydown', handleKeyPress); window.removeEventListener('scroll', handleScroll); window.removeEventListener('resize', handleResize); }; }, []);
This works, but you will need to keep those function references around just so you can pass the same reference to both functions. Using AbortController
, it looks like this:
useEffect(() => { const controller = new AbortController(); const { signal } = controller; // Define all your handler functions const handleMouseMove = (e) => { /* update state */ }; const handleKeyPress = (e) => { /* update state */ }; const handleScroll = () => { /* update state */ }; const handleResize = () => { /* update state */ }; // Add all the listeners with the signal document.addEventListener('mousemove', handleMouseMove, { signal }); document.addEventListener('keydown', handleKeyPress, { signal }); window.addEventListener('scroll', handleScroll, { signal }); window.addEventListener('resize', handleResize, { signal }); // Just one line for cleanup! return () => controller.abort(); }, []);
You can clean everything up with just one line, no matter the number of event listeners available. This is cleaner and less prone to error.
Most examples online you may find using AbortController
in React are mostly implemented within a useEffect()
. However, you don’t necessarily need a useEffect()
to use an AbortController
in React.
You can constantly use the AbortController
for fetch requests in React, as well.
Consider, for example, a project where I will need a search feature that would fire off API requests as the user types.
The problem will be that if the user types quickly, I’ll end up with multiple requests in flight. Sometimes, older requests will finish after newer ones, causing the results to jump around.
Using AbortController
in React can help solve this problem:
// Key implementation of AbortController for API requests in React import { useRef, useState } from 'react'; // Component with search functionality const SearchComponent = () => { const controllerRef = useRef<AbortController>(); const [query, setQuery] = useState<string>(); const [results, setResults] = useState<Array<any> | undefined>(); async function handleOnChange(e: React.SyntheticEvent) { const target = e.target as typeof e.target & { value: string; }; // Update the query state setQuery(target.value); setResults(undefined); // Cancel any previous in-flight request if (controllerRef.current) { controllerRef.current.abort(); } // Create a new controller for this request controllerRef.current = new AbortController(); const signal = controllerRef.current.signal; try { const response = await fetch('/api/search', { method: 'POST', body: JSON.stringify({ query: target.value }), signal }); const data = await response.json(); setResults(data.results); } catch(e) { // Silently catch aborted requests // For production, you might want to check if error is an AbortError } } return ( <div> <input type="text" onChange={handleOnChange} /> {/* Results rendering */} </div> ); };
In the example above, we created a search function that cancels previous API requests when a user types something new. Using useRef
, we’re able to reference and track the current request and abort it each time the input changes.
This pattern will save you countless headaches. With your fingers crossed and a strong belief in AbortController
, you shouldn’t get outdated results showing up after newer ones.
Ordinarily, an asynchronous process may succeed, fail, or take longer than anticipated. Therefore, it is logical to cancel an asynchronous operation that has taken more time than it should or whose results you don’t need. The AbortController
API is a handy functionality for doing just that.
The AbortController
API is globally available; you don’t need to import it. An instance of the AbortController
class exposes the abort
method and the signal
property. The signal
property is an instance of the AbortSignal
class. Each AbortController
class instance has a corresponding AbortSignal
class instance, which you can access using the controller’s signal
property.
You pass the signal
property to a cancelable asynchronous API and invoke the controller’s abort
method to trigger the abort process. If the built-in APIs do not meet your use case, you can also implement a custom abortable API using AbortController
and AbortSignal
. However, follow the best practices hinted above to prevent memory leaks.
I’ll leave you with this; the beauty of the AbortController
API is that you can make virtually any asynchronous operation abortable, even those that don’t natively support cancellation.
Did I miss anything? Leave a comment in the comments section below.
Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If you’re interested in ensuring requests to the backend or third-party services are successful, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens while a user interacts with your app. Instead of guessing why problems happen, you can aggregate and report on problematic network requests to quickly understand the root cause.
LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. Start monitoring for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowReview the basics of react-scripts, its functionality, status in the React ecosystem, and alternatives for modern React development.
Explore the fundamental commands for deleting local and remote branches in Git, and discover more advanced branch management techniques.
LLMs can do more than chat! Explore alternative integration models that improve efficiency, reduce complexity, and enhance user control.
Learn the basics of applying image overlays in CSS and explore more interactive techniques like hover effects and animations.
One Reply to "The complete guide to the <code>AbortController</code> API"
I am qurious in knowing that if we use AbortController in all our fetching request. Is it a good practice or not? In case of no; what’s the reason behind it?