Using AbortController to cancel requests when transitioning between pages in a Single Page Application (SPA)."

There is a fairly common problem where when a user navigates to a page, various data requests for that page start, but suddenly the user quickly switches to another page, and the requests from the previous page continue to execute, still using network resources and performing unnecessary work.

This becomes especially problematic when a user has a slow internet connection, as each additional request further reduces the website's performance.

AbortController is an interface that allows you to control the cancellation of HTTP requests from the frontend.

To begin with, we need to create a service that would instantiate our controller and export methods for managing it:

// abortController.js 

let controller = new AbortController()

export const getControllerSignal = () => controller.signal

export const abortController = () => controller.abort()

export const reinitController = () => {
  controller = new AbortController()
}

Let's go through everything step by step:

  • The signal is essential for passing it into the request so that we can later manage and cancel this request.
  • The abort() method is used specifically to cancel the request to which we have passed the signal.
  • An important point to note is that in order for the requests not to be permanently interrupted after the first time but only when needed, we need to reinitialize our controller. This is where the reinitController method comes into play, which simply calls new AbortController().

Now, we need to pass the signal into all our requests so that we can cancel them. Fortunately, on our project, there is a function that handles all the requests, so we don't have to handle each request individually. In this function, we pass the signal property:

// apiHelper.js

import { getControllerSignal } from '@/services/abortController'

export const callPrivateApi = async(method, url, ...args) => {
  try {
    const { data } = await privateApi[method](url, ...[...args, { signal: getControllerSignal() }])
    ...
  } catch ({ response }) {
    ...
  }
}

In the router file, we'll use the beforeEach hook, which triggers with each page transition. Inside this hook, we need to cancel requests and reinitialize the controller:

// router.js

import { abortController, reinitController } from '@/services/abortController'

...

router.beforeEach(() => {
  abortController()
  reinitController()
})

And that's it! Now, all pending requests will be canceled when changing pages, preventing unnecessary errors and conserving the network resources of users with poor internet connections.

These requests will appear like this, with the status "canceled":

enter image description here

Don't forget, any critical requests that must be executed regardless of page transitions. For instance, requests related to user authentication or data retrieval that occur simultaneously with a redirect should not be subject to cancellation to avoid any adverse consequences.

Source