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 aAbortSignal
instanceAbortController.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 asignal
property. For example:function abortThis(arg1, { signal }) { return new Promise((resolve, reject) => { // function body }); }
- When method
aborted()
is called, reject the Promise with aDOMException
AbortError
throw new DOMException("aborted by user", "ABORT_ERR");
- Abort immediately if the
aborted
flag onAbortSignal
is 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