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 aAbortSignalinstanceAbortController.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
AbortSignalthrough asignalproperty. For example:function abortThis(arg1, { signal }) { return new Promise((resolve, reject) => { // function body }); } - When method
aborted()is called, reject the Promise with aDOMExceptionAbortErrorthrow new DOMException("aborted by user", "ABORT_ERR"); - Abort immediately if the
abortedflag onAbortSignalis set totrue.
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.
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