AbortController recently hit 75% support by browser usage (according to caniuse.com), so let's take a look at what it is and how we can use it to abort fetch requests.
The AbortController
is a Controller exposed by the browser DOM API, which allows us to 'abort' any DOM request. While AbortController
can technically be used to abort any promise, in my usage so far, I've only found it actually useful at cancelling fetch requests. For pretty much any other promise, it is simply sugar, which allows you to listen for an event and reject your promise based on the event, irrespective of any other processing that may be ongoing.
Please Note: Since the AbortController
is a DOM API, it is not available in Node.JS. There are polyfills available which you can use instead.
So let's dive into AbortController
.
Lets first create a new AbortController
, so we can explore more by poking and prodding at it. To create a new AbortController
, instantiate one using the new
keyword.
const abortController = new AbortController();
The AbortController
constructor does not accept any parameters. Now let's inspect the abortController
, and see what properties and methods it has. I'll be using the inspect
method available in Firefox's DevTools console.
inspect(abortController);
As you can see abortController
has a single property signal
which is an instance of AbortSignal
. signal
in turn has two properties:
aborted
: A boolean value which indicates whether or not that particular signal has been aborted yet. Currently theabortController.signal
signal has not been aborted so it's value isfalse
.onabort
: This is an event handler which is invoked when theabort
event of theabortController
fires. Since we currently have not registered any handlers, this isnull
.
Apart from the signal
property, abortController
also has a single method called abort
. As the name indicates, this is the method that is called to abort a particular signal.
So now that we know what the internals of abortController
look like, let's see how we can use abortController
to cancel a generic promise.
Let's assume we have a method executeAfterDelay
. The method accepts a function and a delay in milliseconds, and once the delay has elapsed, it calls the function.
function executeAfterDelay(fn, delay){
const timerId = window.setTimeout(() => {
return fn();
}, delay);
console.log(`Delayed execution setup on timer ${timerId}.`);
};
Now let's say we want to be able to cancel the execution of the provided function after setting it up. To do this we can use the AbortController
as follows:
function executeAfterDelay(fn, delay){
const abortExecuteAfterDelay = new AbortController();
const {signal} = abortExecuteAfterDelay;
const timerId = window.setTimeout(() => {
if(!signal.aborted)
return fn();
}, delay);
console.log(`Delayed execution setup on timer ${timerId}.`);
signal.addEventListener('abort', () => {
console.log(`Abort event fired on signal. Aborting execution on timer ${timerId}.`);
window.clearTimeout(timerId);
});
return abortExecuteAfterDelay;
};
What we have done here is that, we have modified executeAfterDelay
such that it always returns an AbortController
instance. Calling the abort
method on this instance will abort that particular delayed function call.
Now let's run this code and test it out:
let execution = executeAfterDelay(console.log.bind(console, 'Hi there'), 5000);
This will basically ask console.log
to print Hi there
, after a delay of 5 seconds. Running this we get the following result:
Now let's try again, and this time let's try to abort the delayed execution of console.log
.
execution = executeAfterDelay(console.log.bind(console, 'Hi there'), 5000);
execution.abort(); // Type this in before the 5 second timeout expires
As you can see, the timer gets cancelled, and console.log
is never called.
From the example so far, you might be wondering exactly what makes this any different from any other EventEmitter
? All we are doing is emitting an abort
event, and cancelling an action based on that event. And you would be quite right. So far, what we have done is in no way different from any normal EventEmitter
.
But what makes AbortController
and it's abort
event different than other EventEmitter
s is that, most browsers have built in support in native methods to accept an AbortSignal
from and AbortController
and automatically abort an asynchronous method when the abort
method is called on that AbortController
.
As far as I know, currently the only async DOM method where this has been implemented by both Chrome and Firefox is in the fetch
method used to make asynchronous HTTP(S) requests. Safari, does not yet support the AbortController
and it's implementation is a mere stub, which doesn't actually abort any async requests.
So let's look at how to abort a fetch
request in Chrome/Firefox using an AbortController
.
First, let's look at a normal fetch
request. The below requests makes a call to Github's gist API, and returns a list of available gists.
async function getGists(){
const response = await fetch('https://api.github.com/gists');
const gists = await response.json();
console.log(gists);
};
getGists();
Upon executing this, we get a list of public gists.
Now let's try to abort the fetch
request before it is resolved.
In order to abort the fetch
request, we need to pass in an instance of the AbortSignal
to the fetch
method, as the signal
option.
let abortController = new AbortController();
let { signal } = abortController;
async function getGists(){
const response = await fetch('https://api.github.com/gists', { signal });
const gists = await response.json();
console.log(gists);
};
Here we are creating a new AbortController
called abortController
and passing it's signal
property as the signal
option to the fetch
method.
Let's run this:
getGists();
Now let's abort the request before it resolves:
getGists();
abortController.abort();
As you can see, the getGists
method threw an AbortError
. This is because, when we pass a signal
option to the fetch
method, it adds it's own abort
event listeners, and when the signal is aborted by calling abortController.abort
, it terminates the network request, and throws an AbortError
. We can then catch the AbortError
in our code, and handle it as we require.
Please Note: If we attempt to abort
a fetch
request after it has been resolved, no errors are thrown.
And that's all there is to it. It only takes 3 additional lines of code to abort a fetch
request, and in that, all we do is create a new AbortController
, pass in the signal
property of the new instance as the signal
option to the fetch
method, and then finally, use the abort
method to trigger the fetch
to abort.
I'd love to hear your opinion on things I've written in this article. Unfortunately, I've not yet found a nice lightweight comments widget to add on to my blog, so until then the only way I can take comments is via email. Do write to me at asleepysamurai at google's mail service.