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);

Inspect abortController response

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 the abortController.signal signal has not been aborted so it's value is false.
  • onabort: This is an event handler which is invoked when the abort event of the abortController fires. Since we currently have not registered any handlers, this is null.

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:

Non aborted execution

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

Aborted execution

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 EventEmitters 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.

Gist non aborted

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();

Gist non aborted with signal

Now let's abort the request before it resolves:

getGists();
abortController.abort();

Gist aborted with signal

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.

Gist aborted with signal after resolution

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.