Talking about the methods of canceling requests and preventing repeated requests in axios

  • 2021-11-13 06:38:50
  • OfStack

Preface to the table of contents
Core-CancelToken
Practical application and packaging
1 Some small details

Preface

In a real project, we may need to "anti-shake" the request. The main purpose here is to prevent the user from clicking a button repeatedly in a short time in some cases, which leads to the front-end sending multiple requests to the back-end repeatedly. Here I list two common practical situations:

PC End-Two search requests may be triggered when the user double-clicks the search button Mobile terminal-because there is no click delay on the mobile terminal, it is easy to cause misoperation or multiple operations, resulting in retransmission of requests

This may still happen with an Loading mask, so consider the front-end approach to blocking duplicate requests.

Core-CancelToken

The core method of canceling requests in Axios is CanelToken. In the official website document, there are two ways to use CancelToken, which are simply pasted here and added comments

Method 1:


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

axios.get('/user/12345', {
  //  You must perform the request cancelToken Settings 
  cancelToken: source.token
}).catch(function (thrown) {
  //  If the request is canceled, enter the method judgment 
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message);
  } else {
    // handle error
  }
});

//  Cancel the above request 
// source.cancel('messge') message Is optional and must be String
source.cancel('Operation canceled by the user.');

Method 2:


const CancelToken = axios.CancelToken;
let cancel;

axios.get('/user/12345', {
  //  In options Create directly in the 1 A cancelToken Object 
  cancelToken: new CancelToken(function executor(c) {
    cancel = c;
  })
});

//  Cancel the above request 
cancel();

Practical application and packaging

The core methods in axios have been exemplified above, but in practice, we often do not use them as in the official website example, and more do global configuration management in the interceptor of axios. In this case, we need to make some changes to the above code.

Here is the general idea of my realization:

We need to cache all in-progress requests. Before the request is initiated, it is judged whether the request is in progress in the cache list, and if so, the request is cancelled. After any request is completed, you need to delete the request from the cache list so that you can resend the request

When the train of thought is finished, let's go directly to the code


//  List of in-progress requests 
let reqList = []

/**
 *  Prevent duplicate requests 
 * @param {array} reqList -  Request cache list 
 * @param {string} url -  Current request address 
 * @param {function} cancel -  Request interrupt function 
 * @param {string} errorMessage -  Error message to be displayed when requesting interrupt 
 */
const stopRepeatRequest = function (reqList, url, cancel, errorMessage) {
  const errorMsg = errorMessage || ''
  for (let i = 0; i < reqList.length; i++) {
    if (reqList[i] === url) {
      cancel(errorMsg)
      return
    }
  }
  reqList.push(url)
}

/**
 *  Allow a request to continue 
 * @param {array} reqList  List of all requests 
 * @param {string} url  Request address 
 */
const allowRequest = function (reqList, url) {
  for (let i = 0; i < reqList.length; i++) {
    if (reqList[i] === url) {
      reqList.splice(i, 1)
      break
    }
  }
}

const service = axios.create()

//  Request interceptor 
service.interceptors.request.use(
  config => {
 let cancel
   //  Settings cancelToken Object 
    config.cancelToken = new axios.CancelToken(function(c) {
     cancel = c
    })
    //  Prevent duplicate requests. When the last request is not completed, the same request will not be made 
    stopRepeatRequest(reqList, config.url, cancel, `${config.url}  Request interrupted `)
    return config
  },
  err => Promise.reject(err)
)

//  Response interceptor 
service.interceptors.response.use(
  response => {
    //  Increase the delay, and the same request shall not be sent repeatedly in a short time 
    setTimeout(() => {
      allowRequest(reqList, response.config.url)
    }, 1000)
    // ... Subsequent actions after successful request 
    // successHandler(response)
  },
  error => {
    if (axios.isCancel(thrown)) {
      console.log(thrown.message);
    } else {
      //  Increase the delay, and the same request shall not be sent repeatedly in a short time 
      setTimeout(() => {
        allowRequest(reqList, error.config.url)
      }, 1000)
    }
    // ... Subsequent actions after a failed request 
    // errorHandler(error)
  }
)

1 Some small details

Why didn't you set up cancelToken with the code in method 2 above?
There is one note in the documentation for axios:

Note: you can cancel several requests with the same cancel token.
You can cancel multiple requests using the same Token

So I don't want to have new1 new objects before every request
Be sure to use Method 2 to ensure that cancel can be executed correctly every time. The previous method causes subsequent requests to persist cancel when cancel occurs

Why do you need to increase latency in response?
Because you don't want users to repeat the same request in a very short time.
Note that blocking requests in response and blocking requests in request are two concepts:
In request, you are prevented from starting the same request when the previous request is not completed
In response, the same request is prevented from being allowed for 1 period after the last request is completed

Can I pass an object at cancel instead of just message?
You can resack the official source code, or use a third-party plug-in, or use another method: convert the object to an JSON string, and then convert it back where necessary


Related articles: