In the process of Web project development, we often encounter the scenario of repeated requests. If the system does not handle the repeated requests, it may cause various problems in the system. For example, repeated POST requests may cause the server to generate two records. So how do repeated requests come about? Here are two common scenarios:

  • Suppose you have a button on your page that the user clicks and makes an AJAX request. If the button is not controlled, repeated requests are made when the user quickly clicks the button.
  • Suppose in the test results query page, the user can query the test results according to the “passed”, “failed” and “all” three search conditions. If the response to the request is slow, repeated requests will occur when the user switches quickly between different query conditions.

Now that you know how duplicate requests arise, you know that they can cause problems. Next, Using Axios as an example, Apogo will take you through the problem of repetitive requests.

Pay attention to the “full stack of the road to repair the immortal” to read a baoge original 4 free e-books (accumulated downloads of 30 thousand +) and 11 Vue 3 advanced series tutorial.

How to cancel a request

Axios is a Promise-based HTTP client that supports both browsers and node.js environments. It is an excellent HTTP client and is widely used in a large number of Web projects. For the browser environment, the underlying Axios is the XMLHttpRequest object to initiate the HTTP request. To cancel the request, we can do so by calling the ABORT method on the XMLHttpRequest object:

let xhr = new XMLHttpRequest();
xhr.open("GET"."https://developer.mozilla.org/".true);
xhr.send();
setTimeout(() = > xhr.abort(), 300);
Copy the code

In the case of Axios, we can cancel the request with a CancelToken provided internally by Axios:

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

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

source.cancel('Operation canceled by the user.'); // Cancel the request. Parameters are optional
Copy the code

Alternatively, you can create a CancelToken by calling the CancelToken constructor, as shown below:

const CancelToken = axios.CancelToken;
let cancel;

axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) { cancel = c; })}); cancel();// Cancel the request
Copy the code

Now that we know how to use a CancelToken in Axios to cancel a request, how does the CancelToken work internally? Let’s keep that in mind here, and we’ll reveal the secret behind the CancelToken. Next, let’s look at how to identify duplicate requests.

How to judge the repeated request

When the request mode, request URL, and request parameters are the same, we can assume that the request is the same. Therefore, each time we make a request, we can generate a unique key according to the request mode, request URL address and request parameters of the current request. At the same time, we can create a unique CancelToken for each request, and then save the key and cancel functions in the form of key and value pairs in the Map object. The advantage of using Map is that you can quickly determine if there are duplicate requests:

import qs from 'qs'

const pendingRequest = new Map(a);// GET -> params; POST -> data
const requestKey = [method, url, qs.stringify(params), qs.stringify(data)].join('&'); 
const cancelToken = new CancelToken(function executor(cancel) {
  if(!pendingRequest.has(requestKey)){
    pendingRequest.set(requestKey, cancel);
  }
})
Copy the code

When there is a duplicate request, we can use the cancel function to cancel the previously issued request. After canceling the request, we need to remove the cancelled request from the pendingRequest. Now that we know how to cancel a request and how to identify a duplicate request, let’s look at how to cancel a duplicate request.

How to cancel the repeated request

Since we need to process all requests, we can consider using Axios’s interceptor mechanism to implement the ability to cancel duplicate requests. Axios provides developers with request and response interceptors that do the following:

  • Request interceptor: This interceptor is used to perform certain operations uniformly before the request is sent, such as adding a token field to the request header.
  • Response interceptor: This type of interceptor is used to perform certain actions after receiving a server response, such as automatically jumping to the login page when the response status code is found to be 401.

3.1 Define helper functions

Before configuring the request and response interceptors, Apog defines three helper functions:

  • generateReqKey: is used to generate a request Key based on the current request information.
function generateReqKey(config) {
  const { method, url, params, data } = config;
  return [method, url, Qs.stringify(params), Qs.stringify(data)].join("&");
}
Copy the code
  • addPendingRequest: adds the current request information to the pendingRequest object.
const pendingRequest = new Map(a);function addPendingRequest(config) {
  const requestKey = generateReqKey(config);
  config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) = > {
    if (!pendingRequest.has(requestKey)) {
       pendingRequest.set(requestKey, cancel);
    }
  });
}
Copy the code
  • removePendingRequest: Checks whether duplicate requests exist. If yes, cancel the sent requests.
function removePendingRequest(config) {
  const requestKey = generateReqKey(config);
  if (pendingRequest.has(requestKey)) {
     constcancelToken = pendingRequest.get(requestKey); cancelToken(requestKey); pendingRequest.delete(requestKey); }}Copy the code

With the generateReqKey, addPendingRequest, and removePendingRequest functions created, we are ready to set up request interceptors and response interceptors.

3.2 Setting the request Interceptor

axios.interceptors.request.use(
  function (config) {
    removePendingRequest(config); // Check whether there is a duplicate request, if so, cancel the request
    addPendingRequest(config); // Add the current request information to the pendingRequest object
    return config;
  },
  (error) = > {
     return Promise.reject(error); });Copy the code

3.3 Setting the Response Interceptor

axios.interceptors.response.use(
  (response) = > {
     removePendingRequest(response.config); // Remove the request from the pendingRequest object
     return response;
   },
   (error) = > {
      removePendingRequest(error.config || {}); // Remove the request from the pendingRequest object
      if (axios.isCancel(error)) {
        console.log("Cancelled duplicate request:" + error.message);
      } else {
        // Add exception handling
      }
      return Promise.reject(error); });Copy the code

Because the complete sample code content is more, a baoge does not put the specific code. Interested partners, can visit the following address to browse the sample code.

The complete sample code: gist.github.com/semlinker/e…

Here’s a look at the results of the Axios example of canceling duplicate requests:

This example is just to verify that the interceptor is working properly. In practice, the repeated requests caused by button operations can be controlled by adding status bits. Thanks to “@bozhishu” for your advice.

As you can see from the figure above, when a duplicate request occurs, previously sent and incomplete requests are cancelled. Let’s use a flowchart to summarize the process of canceling repeated requests:

Finally, let’s answer the question left, how does the CancelToken work inside?

4. How the CancelToken works

In the previous example, we created the CancelToken object by calling the CancelToken constructor:

new axios.CancelToken((cancel) = > {
  if (!pendingRequest.has(requestKey)) {
    pendingRequest.set(requestKey, cancel);
  }
})
Copy the code

So, let us analysis CancelToken constructor, the function is defined in the lib/cancel/CancelToken js file:

// lib/cancel/CancelToken.js
function CancelToken(executor) {
  if (typeofexecutor ! = ='function') {
    throw new TypeError('executor must be a function.');
  }

  var resolvePromise;
  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
  });

  var token = this;
  executor(function cancel(message) { // Set the Cancel object
    if (token.reason) {
      return; // Cancellation has already been requested
    }
    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}
Copy the code

As you can see from the above code, the Cancel object is a function that, when called, creates the cancel object and calls the resolvePromise method. After this method is executed, the Promise object pointed to by the Promise attribute on the CancelToken object will change to the resolved state. So what’s the purpose of this? Here we find the answer in the lib/adapters/xhr.js file:

// lib/adapters/xhr.js 
if (config.cancelToken) {
  config.cancelToken.promise.then(function onCanceled(cancel) {
    if(! request) {return; }
    request.abort(); // Cancel the request
    reject(cancel);
    request = null;
  });
}
Copy the code

After reading the above content, some friends may not understand the working principle of CancelToken, so Po brother drew a picture to help you understand the working principle of CancelToken:

Five, the summary

This article shows how cancellations and cancelTokens work in Axios, noting that cancellations may have reached the server, in which case the corresponding interface on the server needs to be idempotent controlled. In a future article, Abo will explain how to set up the data cache in Axios. If you want to learn more about the design and implementation of HTTP interceptors and HTTP adapters in Axios, read what to Learn from the 77.9K Axios project.

Pay attention to the “full stack of the road to repair the immortal” to read a baoge original 4 free e-books (accumulated downloads of 30 thousand +) and 11 Vue 3 advanced series tutorial. If you want to learn TS/Vue 3.0 together, you can add Semlinker on wechat.

6. Reference Resources

  • Github – Axios
  • MDN – XMLHttpRequest
  • What can be learned from the 77.9K Axios project