Using React to understand Abort Controllers

Joseph Chamochumbi
4 min readJul 16, 2021

--

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.

An example using React’s useEffect.

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.

Without proper clean up on user id change, Joe’s data takes over Jane’s.

And there’s the AbortController, which fits both cases. You’d use this in a React useEffect hook like this:

Clean up fetch inside useEffect using Abort Controller.

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.

You can read the controller.signal.aborted flag.

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 trigger illegal 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:

Generate, almost, but not quite, unique identifications.

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:

A random id generator. Handled by the 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.

App to render a random id, using Resource.

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.

Listen to the abort event on the signal.

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 in useEffect 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.

A final version of a promise that can be cancelled using an Abort Controller.

By placing an event listener on the controller signal, if available, we can cancel a promise, by calling the controller abort method.

Happy hacking!

--

--

Joseph Chamochumbi

Señor Developer. I do full-stack with JS & TS. Currently mastering Rust.