As we know, fetch
returns a promise. And JavaScript generally has no concept of “aborting” a promise. So how can we cancel an ongoing fetch
? E.g. if the user actions on our site indicate that the fetch
isn’t needed any more.
There’s a special built-in object for such purposes: AbortController
. It can be used to abort not only fetch
, but other asynchronous tasks as well.
The usage is very straightforward:
The AbortController object
Create a controller:
let
controller =
new
AbortController
(
)
;
A controller is an extremely simple object.
- It has a single method
abort()
, - And a single property
signal
that allows to set event listeners on it.
When abort()
is called:
controller.signal
emits the"abort"
event.controller.signal.aborted
property becomestrue
.
Generally, we have two parties in the process:
- The one that performs a cancelable operation, it sets a listener on
controller.signal
. - The one that cancels: it calls
controller.abort()
when needed.
Here’s the full example (without fetch
yet):
let
controller =
new
AbortController
(
)
;
let
signal =
controller.
signal;
// The party that performs a cancelable operation
// gets the "signal" object
// and sets the listener to trigger when controller.abort() is called
signal.
addEventListener
(
'abort'
,
(
)
=>
alert
(
"abort!"
)
)
;
// The other party, that cancels (at any point later):
controller.
abort
(
)
;
// abort!
// The event triggers and signal.aborted becomes true
alert
(
signal.
aborted)
;
// true
As we can see, AbortController
is just a mean to pass abort
events when abort()
is called on it.
We could implement the same kind of event listening in our code on our own, without the AbortController
object.
But what’s valuable is that fetch
knows how to work with the AbortController
object. It’s integrated in it.
Using with fetch
To be able to cancel fetch
, pass the signal
property of an AbortController
as a fetch
option:
let
controller =
new
AbortController
(
)
;
fetch
(
url,
{
signal
:
controller.
signal
}
)
;
The fetch
method knows how to work with AbortController
. It will listen to abort
events on signal
.
Now, to abort, call controller.abort()
:
controller.
abort
(
)
;
We’re done: fetch
gets the event from signal
and aborts the request.
When a fetch is aborted, its promise rejects with an error AbortError
, so we should handle it, e.g. in try..catch
.
Here’s the full example with fetch
aborted after 1 second:
// abort in 1 second
let
controller =
new
AbortController
(
)
;
setTimeout
(
(
)
=>
controller.
abort
(
)
,
1000
)
;
try
{
let
response =
await
fetch
(
'/article/fetch-abort/demo/hang'
,
{
signal
:
controller.
signal
}
)
;
}
catch
(
err)
{
if
(
err.
name ==
'AbortError'
)
{
// handle abort()
alert
(
"Aborted!"
)
;
}
else
{
throw
err;
}
}
AbortController is scalable
AbortController
is scalable. It allows to cancel multiple fetches at once.
Here’s a sketch of code that fetches many urls
in parallel, and uses a single controller to abort them all:
let
urls =
[
...
]
;
// a list of urls to fetch in parallel
let
controller =
new
AbortController
(
)
;
// an array of fetch promises
let
fetchJobs =
urls.
map
(
url
=>
fetch
(
url,
{
signal
:
controller.
signal
}
)
)
;
let
results =
await
Promise.
all
(
fetchJobs)
;
// if controller.abort() is called from anywhere,
// it aborts all fetches
If we have our own asynchronous tasks, different from fetch
, we can use a single AbortController
to stop those, together with fetches.
We just need to listen to its abort
event in our tasks:
let
urls =
[
...
]
;
let
controller =
new
AbortController
(
)
;
let
ourJob =
new
Promise
(
(
resolve,
reject
)
=>
{
// our task
...
controller.
signal.
addEventListener
(
'abort'
,
reject)
;
}
)
;
let
fetchJobs =
urls.
map
(
url
=>
fetch
(
url,
{
// fetches
signal
:
controller.
signal
}
)
)
;
// Wait for fetches and our task in parallel
let
results =
await
Promise.
all
(
[
...
fetchJobs,
ourJob]
)
;
// if controller.abort() is called from anywhere,
// it aborts all fetches and ourJob
Summary
AbortController
is a simple object that generates anabort
event on itssignal
property when theabort()
method is called (and also setssignal.aborted
totrue
).fetch
integrates with it: we pass thesignal
property as the option, and thenfetch
listens to it, so it’s possible to abort thefetch
.- We can use
AbortController
in our code. The “callabort()
” → “listen toabort
event” interaction is simple and universal. We can use it even withoutfetch
.
Comments
<code>
tag, for several lines – wrap them in<pre>
tag, for more than 10 lines – use a sandbox (plnkr, jsbin, codepen…)