Terminating DOM Ops and HTTP requests using `AbortController` in JavaScript

AbortController is an interface which provides a way for terminating one or more web request as and when desired.

This generally means that a request can be terminated by a user whenever needed, irrespective of whether the operation is finished or not. AbortSignal can be implemented in any web platform API which uses Promise.

The API

AbortController provides a few things for users to implement it effectively in code. At the time of writing this, the constructor would return an instance which contains a method AbortController.abort() and a property AbortController.signal(read-only).

  • AbortController.signal - Returns a AbortSignal instance
  • AbortController.abort() - Aborts a DOM Request

AbortSignal

AbortController.signal returns an instance of type AbortSignal which represents the current state of the AbortController instance. It has a read-only property AbortSignal.aborted. Type of property aborted is Boolean.

AbortSignal is also responsible for communicating with DOM as an Event Listener can be attached to it.

Using AbortController

To use AbortController in a Promise, one must adhere to a few rules. Source

  • Function should accept AbortSignal through a signal property. For example:
    function abortThis(arg1, { signal }) {
      return new Promise((resolve, reject) => {
        // function body
      });
    }
    
  • When method aborted() is called, reject the Promise with a DOMException AbortError
    throw new DOMException("aborted by user", "ABORT_ERR");
    
  • Abort immediately if the aborted flag on AbortSignal is set to true.

Browser Support

Despite being a relatively new API, browser support for AbortController is quite awesome! All major browser fully supports it. Following is a chart from Can I Use.

go to chart

Data on support for the {{ .Get "feature" }} feature across the major browsers

Examples

Following are a few examples of AbortController API. First one is using fetch API. On the second example, I will try to implement it in a Promise.

Using Fetch API

const controller = new AbortController();
const signal = controller.signal;

// fetch a URL
fetch("https://jsonplaceholder.typicode.com/todos/1", { signal })
  .then((raw) => raw.ok && raw.json())
  .then(console.log)
  .catch((e) => console.log(e.message));

// driver code
setTimeout(() => {
  // abort network request if it takes
  // more than a second
  controller.abort();
}, 500);

The above code allows a network request to run for 500ms. If data has been fetched within that period, it would return JSON representation of the response. Otherwise, it would abort the request.

Implementing using Promise

In this section, I will try to implement AbortController for a function which returns Promise. Should be quite easy and straightforward. Here is a Gist for the same.

/* this function would return a Promise
 * which would resolve with value `hello`
 * after 4s
 */
function abortTask({ signal }) {
  return new Promise((resolve, reject) => {
    // 1. check if it's already aborted
    if (signal && signal.aborted) {
      return reject(
        new DOMException("`signal` is in `aborted` state", "ABORT_ERR")
      );
    }

    // wait for 4s
    setTimeout(() => resolve("hello"), 4000);

    // 2. add a listener to `signal` to check its state
    signal &&
      signal.addEventListener("abort", () => {
        // reject promise when signal.aborted changes to `true`
        return reject(new DOMException("aborted by user", "ABORT_ERR"));
      });
  });
}

/* DRIVER CODE */
let signal; // just so that I could re-use it

// when `signal.aborted` is `false`
const abortController_1 = new AbortController();
signal = abortController_1.signal;

abortTask({ signal })
  .then((t) => console.log(t)) // hello
  .catch((e) => console.error(e.message));

// when `signal.aborted` is `true`
const abortController_2 = new AbortController();
signal = abortController_2.signal;

abortController_2.abort();

abortTask({ signal })
  .then((t) => console.log(t))
  .catch((e) => console.error(e.message)); // err

// when user calls AbortController.abort()
const abortController_3 = new AbortController();
signal = abortController_3.signal;

// abort task in 2s
setTimeout(() => abortController_3.abort(), 2000);

abortTask({ signal })
  .then((t) => console.log(t))
  .catch((e) => console.error(e.message)); // err