How to cancel Ajax? Do you need to cancel it?

·

5 min read

Ajax cancel

If you are familiar with xhr, you know that Ajax can be canceled in front end using XMLHttpRequest.abort(). Of course, this is not the era of slash-and-burn cultivation. Except for interviews, no body makes you to write xhr from from scratch. For the ubiquitous axios, there two ways to cancel ajax:

The first way is the old-fashioned CancelToken:

const CancelToken = axios.CancelToken
const source = CancelToken.source()

axios
  .get('/user/12345', {
    cancelToken: source.token,
  })
  .catch(function (thrown) {
    if (axios.isCancel(thrown)) {
      console.log('Request canceled', thrown.message)
    } else {
      // handle error
    }
  })

axios.post(
  '/user/12345',
  {
    name: 'new name',
  },
  {
    cancelToken: source.token,
  }
)

// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.')

And the second way is the new (not really new in fact) AbortController:

const controller = new AbortController()

axios
  .get('/foo/bar', {
    signal: controller.signal,
  })
  .then(function (response) {
    //...
  })
// cancel the request
controller.abort()

No matter you use AbortController or CancelToken, axios will execute XMLHttpRequest.abort() under the hood.

onCanceled = (cancel) => {
  if (!request) {
    return
  }
  reject(
    !cancel || cancel.type ? new CanceledError(null, config, request) : cancel
  )
  request.abort()
  request = null
}

config.cancelToken && config.cancelToken.subscribe(onCanceled)
if (config.signal) {
  config.signal.aborted
    ? onCanceled()
    : config.signal.addEventListener('abort', onCanceled)
}

cancelToken uses the publish-subscribe model to notify axios to cancel the request. Although this part is implemented by axios, it originated from a tc39 abandoned proposal cancelable promises.

AbortController is an interface that can already be used in browsers. As its name suggests, this is a controller for aborting action. Mdn use Ajax request for an instance, but they use fetch which is more popular now. It can be seen that the practice of axios and fetch is consistent:

 function fetchVideo() {
  controller = new AbortController()
  const signal = controller.signal
  fetch(url, { signal })
    .then(function (response) {
      console.log('Download complete', response)
    })
    .catch(function (e) {
      console.log('Download error: ' + e.message)
    })
}

abortBtn.addEventListener('click', function () {
  if (controller) controller.abort()
  console.log('Download aborted')
})

Other uses of AbortController

Of course, AbortController not only use to cancel Ajax. We can find two more usage in the dom specification document :

A more practical example is to cancel the event listener with AbortController:

 dictionary AddEventListenerOptions : EventListenerOptions {
  boolean passive = false;
  boolean once = false;
  AbortSignal signal;
};

By passing the signal to the AddEventListener, we can run abort() to cancel the event listener. This method is especially useful while we pass anonymous function to AddEventListener.

Another example is for aborting promises . This is a relatively simple and self-documenting method... But in fact, it is not necessary to AbortController to achieve the goal, as long as you find a way to get the reject function of the promise. I think the point of this example is to learn to use the onabort event:

const controller = new AbortController();
const signal = controller.signal;

startSpinner();

doAmazingness({ ..., signal })
  .then(result => ...)
  .catch(err => {
    if (err.name == 'AbortError') return;
    showUserErrorMessage();
  })
  .then(() => stopSpinner());

// …

controller.abort();

function doAmazingness({signal}) {
  return new Promise((resolve, reject) => {
    signal.throwIfAborted();

    // Begin doing amazingness, and call resolve(result) when done.
    // But also, watch for signals:
    signal.addEventListener('abort', () => {
      // Stop doing amazingness, and:
      reject(signal.reason);
    });
  });
}

In short, signal is a simple transmitter, and its functionality is to canceling an operation. If you don't want to implement a pubsub object yourself in some case, use this and you're done.

This is the end of the introduction of AbortController. I wonder if everyone has forgotten the title of this post... Finally, I want to discuss whether it is useful to cancel Ajax?

To cancel or not to cancel, that is the question

In fact, Ajax cancellation is just for the front-end itself. In the view of the back-end programs, they doesn't know the request is aborted. So requests passed to the server still need to be executed. If the back-end program does not have specialized processing, it will still run hard for your 10s cost request.

So, does the "optimization" seen in some articles, the so-called "cancel requests, and only keep the last request" really makes sense?

That's a case-by-case thing. For requests to modify data such as POST, even if the request is slow every time, the server is already processing it. It is meaningless to cancel the previous POST and send it again.

For GET, and only for some extreme operations, ajax cancellation may have a little effect. For example, there is a request to fetch very large table data. The result is not obtained, and then the user uses search to quickly return a small amount of data and render it. Later, the super large table data is actually returned and it will overwrite the searched data. Here is ajax should be cancelled. There is also the cancellation of download and upload, but it is rare case.

Finally, there is a reasonable but in fact useless benefit: reduce request quantity. After all, the number of simultaneous requests for a domain name in the browser is limited. In more cases, setting the timeout option is more practical. Hmm...unless there are five or six super slow requests lined up at the same time...

My personal suggestion is that cancellation is a special treatment for very specific cases. It's good to know this, but there is no need to actually use it.

And, What do you think?