Abort Controller: How To Stop Processing Duplicate Requests Out of Order

Abort Controller: How To Stop Processing Duplicate Requests Out of Order

Use JavaScript's AbortController to cancel requests which are no longer needed

·

4 min read

To do something asynchronous in JavaScript you typically need to use a Promise.

Need to open a local file?

Need to make an API request using fetch?

Want to wrap setTimeout to use with async / await?

You can use a promise. It's a common pattern that's used everywhere. The Promise pattern is a way to clean up your code so it's easier to write and understand.

The Problem

Ok. But, what happens if we fire off an async task and then no longer care about the result of the Promise?

Let's say I made the same API request two times in a row. I don't care about the result of the first one, but API requests are async. They might come back in the wrong order.

OutOfOrderRequests.png

So what?

It might not seem like a big deal, but if I'm displaying data from an API in the browser, the user could end up in a weird state.

For example, let's say my application has a list with a set of filters. Every time I change the set of filters, I need to make a new API request to get the filtered set of data. Now if I try to change the filters rapidly, I'll end up making a ton of requests. Since those requests can come back in any order, things are going to get really weird really fast.

Here's an example of this issue in action.

Notice how rapidly clicking Show Completed updates the list multiple times. The list also ends up in the wrong state.

The Solution

So how do you fix the problem?

You can use an AbortController.

What the heck is an AbortController? Ok. I hear you. Let's take a step back and define a couple of terms:

AbortController

This is what MDN has to say about abort controllers:

The AbortController interface represents a controller object that allows you to abort one or more Web requests as and when desired.

What does that mean for us?

Basically, the AbortController interface returns an object with these two properties:

  • signal - an AbortSignal
  • abort - a function to abort a request

AbortSignal

Once again, this is what MDN has to say about abort signals:

The AbortSignal interface represents a signal object that allows you to communicate with a DOM request (such as a fetch request) and abort it if required via an AbortController object.

The AbortSignal interface returns an object with these two properties:

  • aborted - a boolean that tells you if the request was aborted
  • reason - an optional message to explain why it was aborted

Example

Let's take a look at a rough sketch at how you would use an AbortController:

function getTheThing() {
  const controller = new AbortController();
  // `fetch` takes an optional `signal` property that it uses internally to cancel the request
  // If the request is canceled it will fall into the `catch`
  fetch('https://<appropriate_url>.com', { signal: controller.signal })
    .then(thing => console.log('THING: ', thing))
    .catch(err => console.log('ERROR: ', err));
  return controller;
}

const controller = getTheThing();

// Abort the request if it takes longer than a second
setTimeout(() => controller.abort('optional reason'), 1000);

The above example is ok, but it doesn't clearly show how fetch uses the signal. Things are abstracted away, so it's not obvious.

Here's another basic example so that you can get an idea about how to use things directly:

function getTheThing() {
  const controller = new AbortController();
  setTimeout(() => {
    if (controller.signal.aborted) {
      console.log('We can check the signal to see if it was aborted');
    } else {
      console.log('Do something else if it wasn\'t aborted');
    }
  }, 2000);
}

const controller = getTheThing();

// Abort the request if it takes longer than a second
setTimeout(controller.abort, 1000);

The above example might not be that useful, but you can clearly see how you can check the aborted status on the signal to change the code flow.

How would this apply to the filter example from above? Well, all we'd need to do is just cancel the old request any time a new request is made. In other words, every time you change the filters for your list we cancel the previous request and make a new one.

Let's see how that looks.

Notice how this time the list doesn't update until I stop clicking. It also ends up in the correct state.

Demo

Try it out for yourself to see how things are working:

Conclusion

Sometimes the async code you write needs to be canceled.

It's no longer important to the user flow. The result of the Promise is unnecessary. AbortSignal can help make that process smoother by avoiding collisions in how the results are handled.

AbortSignal can help make sure the code you write is more predictable with less bugs.