Abort Controller: How To Stop Processing Duplicate Requests Out of Order
Use JavaScript's AbortController to cancel requests which are no longer needed
To do something asynchronous in JavaScript you typically need to use a Promise
.
Need to open a local file?
Need to make an API request using fetch?
Want to wrap setTimeout
to use with async / await
?
You can use a promise. It's a common pattern that's used everywhere. The Promise
pattern is a way to clean up your code so it's easier to write and understand.
The Problem
Ok. But, what happens if we fire off an async task and then no longer care about the result of the Promise
?
Let's say I made the same API request two times in a row. I don't care about the result of the first one, but API requests are async. They might come back in the wrong order.
So what?
It might not seem like a big deal, but if I'm displaying data from an API in the browser, the user could end up in a weird state.
For example, let's say my application has a list with a set of filters. Every time I change the set of filters, I need to make a new API request to get the filtered set of data. Now if I try to change the filters rapidly, I'll end up making a ton of requests. Since those requests can come back in any order, things are going to get really weird really fast.
Here's an example of this issue in action.
Notice how rapidly clicking Show Completed updates the list multiple times. The list also ends up in the wrong state.
The Solution
So how do you fix the problem?
You can use an AbortController
.
What the heck is an AbortController
? Ok. I hear you. Let's take a step back and define a couple of terms:
AbortController
This is what MDN has to say about abort controllers:
The AbortController interface represents a controller object that allows you to abort one or more Web requests as and when desired.
What does that mean for us?
Basically, the AbortController
interface returns an object with these two properties:
signal
- anAbortSignal
abort
- a function to abort a request
AbortSignal
Once again, this is what MDN has to say about abort signals:
The AbortSignal interface represents a signal object that allows you to communicate with a DOM request (such as a fetch request) and abort it if required via an AbortController object.
The AbortSignal
interface returns an object with these two properties:
aborted
- a boolean that tells you if the request was abortedreason
- an optional message to explain why it was aborted
Example
Let's take a look at a rough sketch at how you would use an AbortController
:
function getTheThing() {
const controller = new AbortController();
// `fetch` takes an optional `signal` property that it uses internally to cancel the request
// If the request is canceled it will fall into the `catch`
fetch('https://<appropriate_url>.com', { signal: controller.signal })
.then(thing => console.log('THING: ', thing))
.catch(err => console.log('ERROR: ', err));
return controller;
}
const controller = getTheThing();
// Abort the request if it takes longer than a second
setTimeout(() => controller.abort('optional reason'), 1000);
The above example is ok, but it doesn't clearly show how fetch
uses the signal
. Things are abstracted away, so it's not obvious.
Here's another basic example so that you can get an idea about how to use things directly:
function getTheThing() {
const controller = new AbortController();
setTimeout(() => {
if (controller.signal.aborted) {
console.log('We can check the signal to see if it was aborted');
} else {
console.log('Do something else if it wasn\'t aborted');
}
}, 2000);
}
const controller = getTheThing();
// Abort the request if it takes longer than a second
setTimeout(controller.abort, 1000);
The above example might not be that useful, but you can clearly see how you can check the aborted status on the signal to change the code flow.
How would this apply to the filter example from above? Well, all we'd need to do is just cancel the old request any time a new request is made. In other words, every time you change the filters for your list we cancel the previous request and make a new one.
Let's see how that looks.
Notice how this time the list doesn't update until I stop clicking. It also ends up in the correct state.
Demo
Try it out for yourself to see how things are working:
Conclusion
Sometimes the async code you write needs to be canceled.
It's no longer important to the user flow. The result of the Promise
is unnecessary. AbortSignal
can help make that process smoother by avoiding collisions in how the results are handled.
AbortSignal
can help make sure the code you write is more predictable with less bugs.