Using React to understand Abort Controllers
In case you didn’t know, browsers support an API called AbortController
, which is typically used to cancel ongoing fetch requests. More info always available at MDN.
Step by Step
Normally you fetch data and when it resolves you set some state with the response. However, there’s two situations to consider:
- The component querying for this data in unmounted from the DOM.
- The data is not relevant anymore.
The latter can happen for example if you are fetching friends
of a user Joe
, but Joe
has too many friends so the request is slow. Before the request for Joe
‘s friends resolves, you decide you want to see Jane
‘s friends instead, but she has fewer friends so her request goes way faster and you show her friends list almost immediately, but then Joe
‘s request resolves and overrides the friends list. In this case you would have wanted to cancel the first request.
There’s many techniques to do this, for example, for unmounted components, use a ref
on a DOM element, and check if ref.current
is not null when the response comes back, or the less preferred way of having an "is-mounted” ref
flag.
For irrelevant data you could use a variable, which both your request and the effect cleanup have closure over, so when clean up happens you set this variable to a value, and inside your promise chain you check for this value.
And there’s the AbortController
, which fits both cases. You’d use this in a React useEffect
hook like this:
But how does the Abort Controller work exactly?
When the abort method on the controller is called, the fetch operation and every subsequent, then
methods are discarded, and the catch
method is executed.
If you are setting an error state inside your catch block, then you need to differentiate the situation in which the catch
block is called.
if there’s no
catch
the error is passed to the caller. In create-react-app development mode, the error will overlay your page.
To do this, check if the controller.signal.aborted
flag is set to true.
If the controller.signal.aborted
flag is true, then you can skip updating your error state.
If you are into implicit calls and functional programming, or just plain understand JavaScript better than most, you could think of having:
return controller.abort
and use the fact that React will do call for you, but that will triggerillegal invocation
because of loss of function context.
Cancelling any Promise
The ability to cancel a promise is somewhat desirable in JavaScript. There are multiple techniques to do it, but here I’ll make use of the Abort Controller, in order to reinforce its utility.
Say we have discovered a magical function to create unique values, not quite but almost, call it randomId
:
For argument sake, turn this otherwise synchronous work, into a promise. It reads that after 200 milliseconds, we resolve with an id
which is the concatenation of the current instant and a random number.
Let’s use it in a React app, by wrapping it all nicely inside a Resource
component:
And finally render it as part of a React app. I use an image here, because Medium doesn’t allow inline onClick
events in Carbon embeds.
A few things to note:
- We toggle the
Resource
, which triggers an asynchronous process when mounted. - If there’s an ongoing asynchronous process and we toggle down the
Resource
component, then the asynchronous process resolves on an already unmounted component. This triggers a React warning.
Let’s create a mechanism to cancel the process of getting the random, pseudo-unique id.
Pardon me for not using TypeScript
for this one, but in this case signal is an Abort Signal type, which is also an object in the browser API.
This randomId
function can now take an optional signal. If the signal is an instance of AbortSignal
then we attach an event listener to it, which will clear the ongoing timeout, when an abort event happens.
This event listener shall be ran only once.
Then all that’s left to do is to use an AbortController
in useEffect
method inside Resource
:
A link with the entire code can be found here:
What happened?
- When
Resource
unmounted, the clean up function inuseEffect
calls the abort method in the controller. - This triggers an abort event, which our signal is listening to.
- This in turn triggers the cancellation of the timeout that resolves the
id
, and also rejects the promise.
There’s one last thing, when the promise does resolve, we should detach the on abort event listener.
By placing an event listener on the controller signal, if available, we can cancel a promise, by calling the controller abort method.
Happy hacking!