DEV Community

Cover image for Canceling requests in Javascript
Miguel Crespo
Miguel Crespo

Posted on • Originally published at miguelcrespo.co

Canceling requests in Javascript

If you're a developer like me, you probably had to write some custom logic to handle stale requests that you triggered in the past, but whose response you no longer need. But did you know that it's possible to cancel HTTP requests from your code even after triggering them?

There's an API for this specific case and I recently had the opportunity to use it, and let me tell you that it's more useful than what I initially thought. Here are the reasons why you should use it:

Why you should cancel stale HTTP requests?

Cancelling HTTP requests when no longer needed has several advantages and can reduce the complexity of your code by a lot. Some of the benefits you get when you cancel a request instead of just ignoring them are:

  • The resource associated to them get released as well
  • It prevents unnecessary data transfer and the subsequent parsing of the response, which can be a heavy task depending on the size of the response
  • Prevents unexpected but common issues such as accidentally overwriting newer information when a stale request gets resolved
  • Simplifies the code as you no longer need to have custom logic to ignore stale requests
  • It opens the possibility to free resources in the backend as well if you listen to when clients cancel their requests

The API

All the knowledge I have about Javascript tells me that the JS way to cancel a fetch request would be something like:

const promise = fetch('https://dummyjson.com/products').then(res => res.json()).then(console.log);

// On user activity...
promise.abort() // Or .cancel()
Enter fullscreen mode Exit fullscreen mode

But I was wrong, and yet, the real cancelation API is easy and simple. It basically consists of one object called AbortController that has one method .abort(). Although it's weird looking for a JS developer, the API is quite straightforward, and it's also built generic enough to be easily adopted by other APIs.

Canceling a fetch request

Canceling a fetch request is a simple as creating an AbortController instance and passing its .signalproperty as a property to the fetch function:

const controller = new AbortController();

// URL with big file so it takes time to download, taken from mdm web docs
fetch('https://mdn.github.io/dom-examples/abort-api/sintel.mp4', { signal: controller.signal })
  .then(res => {
    console.log("Download completed")
   })
   .catch((err) => {
     console.error(`Download error: ${err.message}`)
   })

const onCancel = () => {
  controller.abort()  
}
Enter fullscreen mode Exit fullscreen mode

Things to note:

  • Reusing an AbortController for a new fetch request will cancel the request immediately if the signal was aborted in the past
  • Aborting a signal after a request has been resolved won't trigger any error

Cancelling multiple fetch requests

You can use one signal to cancel multiple fetch requests:

const controller = new AbortController();

fetch('https://dummyjson.com/products', { signal: controller.signal })
fetch('https://dummyjson.com/products', { signal: controller.signal })
fetch('https://dummyjson.com/products', { signal: controller.signal })
fetch('https://dummyjson.com/products', { signal: controller.signal })

setTimeout(() => {
  controller.abort()
}, 400);
Enter fullscreen mode Exit fullscreen mode

Handling user cancelation errors

One thing to notice is that canceling requests will throw an AbortError so you need to handle them in your catch method. In most of the cases, you just want to ignore them, since they are not really errors.

fetch('https://mdn.github.io/dom-examples/abort-api/sintel.mp4', { signal: controller.signal })
  .then(res => console.log("Download completed"))
   .catch((err) => {
     if (err.name !== 'AbortError') {
       console.error(`Download error: ${err.message}`)
     }
   })
Enter fullscreen mode Exit fullscreen mode

Knowing this will prevent you from getting spammed by your logging system --If you have one--.

What about other libraries?

Axios

Since v0.22.0, Axios supports canceling requests out of the box with a similar API. The only thing to watch out for is that Axios returns a different error for canceled requests: CanceledError.

const controller = new AbortController();

axios.get('https://dummyjson.com/products', { signal: controller.signal })
     .catch((e) => {
        if (e.name !== 'CanceledError') {
          console.error(e);
        }
      });
Enter fullscreen mode Exit fullscreen mode

More details here.

React query

Another popular library that supports request cancelation out of the box is React Query.

The following is an example taken from the official documentation.

const query = useQuery({
  queryKey: ['todos'],
  // Get the signal from the queryFn function
  queryFn: async ({ signal }) => {
    const todosResponse = await fetch('/todos', {
      // Pass the signal to one fetch
      signal,
    })
    const todos = await todosResponse.json()

    const todoDetails = todos.map(async ({ details }) => {
      const response = await fetch(details, {
        // Or pass it to several
        signal,
      })
      return response.json()
    })

    return Promise.all(todoDetails)
  },
})
Enter fullscreen mode Exit fullscreen mode

Food for thought

As said before, the entire cancelation API is quite generic, and it's designed to be implemented by other APIs and be adapted to other usages. The implementations are not only limited to HTTP requests, but could also be used to cancel heavy Javascript logic. For example, the following code could be a costly function that is run somewhere along your entire logic and could use a signal to prevent using resources when not necessary:

const  veryHeavyOperation = (signal) => {
  Array.from({ length: 10 }).forEach((_, index) => {
    if(signal.aborted) return; // If the signal is cancel, don't do anything 🦥
      setTimeout(() => {
        // HEAVY OPERATIONS
        console.log("Heavy Operations: ", index)
       }, 1000);
     });
};
Enter fullscreen mode Exit fullscreen mode

Conclusion

I think this is an underrated API that deserves more credit. It's easy to use, It's supported in all the major browsers, and It could improve a lot the experience of the people using our products, as well as our experience as developers.

Top comments (0)