Description: intercepting duplicate requests is to prevent the system from getting stuck by initiating multiple requests at one time. For example, when the network speed is slow, users frequently Click to initiate requests. I also read a lot of blogs here to learn how to configure better, and then I don't quite understand how axios cancels previous requests. What's the principle?? Many blogs didn't say it, but they just posted the code and also on the official website, but they just understood the general idea, so I'd like to summarize it myself:
You can check the description on the official website first:
Official website description
General principle
(the source code analysis will be posted later. Of course, it's the analysis of the boss, not my chicken):
In fact, we create a cancel token through the CancelToken.source factory method provided on the official website, or create a cancel token by passing an executor function to the CancelToken constructor. Then we request to bring this CancelToken, and then manually call the cancel method after the request to be cancelled. Maybe this is not very clear, For the above two cases, the following details are given:
1. The canceltoken.source factory method creates a cancel token
const CancelToken = axios.CancelToken; const source = CancelToken.source(); // Returns an object containing the token and cancel methods
In this way, an object of token and cancel method will be returned. We will add canceltoken to the requested method. For example, if you have a request on the page, take the one on the official website to demonstrate
const CancelToken = axios.CancelToken; const source = CancelToken.source(); // You called a get method axios.get('/user/12345', { cancelToken: source.token // Bring the cancelToken }).catch(function(thrown) { // Enter here after cancellation if (axios.isCancel(thrown)) { console.log('Request canceled', thrown.message); } else { // Processing error } }); // Or do you call post axios.post('/user/12345', { name: 'new name' }, { cancelToken: source.token // close }) // Cancel the request (the message parameter is optional) // The cancellation request is called here, which will cancel the above request. Of course, this is only for demonstration. Actually, we are not calling here source.cancel('Operation canceled by the user.');
2. Pass an executor function to the constructor of CancelToken
This is to generate cancleToken through the constructor and pass the canclecancel function to the variable defined by ourselves
const CancelToken = axios.CancelToken; // Constructor let cancel; axios.get('/user/12345', { cancelToken: new CancelToken(function executor(c) { // The executor function takes a cancel function as an argument cancel = c; // Give us the defined cancle. Notice that this c is actually a function }) }); // Call the cancel function where you need to cancel cancel(); // Call cancel function
That's about it. How can the project be configured globally instead of once for such a request??
I'll post my code here first. Most of the logic is also based on the Internet
Note: Here I save the request through an array, and then judge whether it is the same request in the interception every time. If so, cancel the previous request. Then the judgment is based on my judgment of the request method. Only when the request address url and request parameters (parameters or data) are all the same can it be regarded as the same request, Otherwise, some request addresses are the same, but the parameters are different
setting1.js
import axios from "axios"; axios.defaults.baseURL = "/api"; let pending = []; //Declare an array to store the cancellation function and ajax ID for each ajax request let cancelToken = axios.CancelToken; let removeRepeatUrl = ever => { for (let p in pending) { // Determine whether there are duplicate requests if ( pending[p].config && pending[p].config.url === ever.url && pending[p].config.method === ever.method ) { if ((isObjectValueEqual(pending[p].config), ever)) //Executes the function body when the current request exists in the array pending[p].cancle(); //Execute cancel operation pending.splice(p, 1); //Remove this record from the array } } }; // request interceptor axios.interceptors.request.use( config => { console.log("config", config); //Perform a cancel operation before sending an ajax removeRepeatUrl({ method: config.method, url: config.url, params: config.params, data: config.data }); // Create cancleToken and cancle cancel request methods. Each request is different config.cancelToken = new cancelToken(c => { // Custom unique ID pending.push({ config: { method: config.method, url: config.url, params: config.params, data: config.data }, cancle: c // }); }); return config; }, err => { return Promise.reject(err); } ); // Response interceptor axios.interceptors.response.use( res => { console.log("response", res); removeRepeatUrl({ method: res.config.method, url: res.config.url, params: res.config.params, data: res.config.data }); //After an ajax response, perform a cancel operation to remove the completed request from pending const data = res.data; return data; }, err => { return Promise.reject(err); } ); export const request = config => { return axios({ ...config }); }; /** *Compares whether two objects are equal * @method isObjectValueEqual * @param {Object} a Object a * @param {Object} b Object b * @return {Boolean} */ function isObjectValueEqual(a, b) { console.log(a, b); // Judge whether two objects point to the same memory, return true when pointing to the same memory, and compare null and undefined conditions at the same time if (a == b) return true; if (a == null || a == undefined || b == null || b == undefined) { return false; } // Gets an array of two object key values let aProps = Object.getOwnPropertyNames(a); let bProps = Object.getOwnPropertyNames(b); // Judge whether the length of two object key value arrays is consistent. If not, false is returned if (aProps.length !== bProps.length) return false; // Traverse the key value of the object for (let prop in a) { // Judge whether the key value of a exists in b. if it does not exist, return false if (b.hasOwnProperty(prop)) { // Judge whether the key value of a is an object. If yes, it will be recursive. It is not an object to directly judge whether the key values are equal. If not, false will be returned if (typeof a[prop] === "object") { if (!isObjectValueEqual(a[prop], b[prop])) return false; } else if (a[prop] !== b[prop]) { return false; } } else { return false; } } return true; }
api method
import { request } from "./setting1"; export function getList(params) { return request({ url: "/list", method: "get", params: params }); } export function getList1(params) { return request({ url: "/list1", params: params, method: "get" }); }
vue file call:
<template> <div> <el-button type="primary" @click="handleGO">Request 1</el-button> <el-button type="primary" @click="handleGet">Request 2</el-button> </div> </template> <script> import { getList, getList1 } from "@/api/index"; export default { name: "WorkspaceJsonIndex", data() { return {}; }, mounted() {}, methods: { handleGO() { getList({ requestName: "11", }).then((res) => { console.log("Request succeeded", res); }); }, handleGet() { getList1().then((res) => { console.log("Request succeeded 11", res); }); }, }, }; </script> <style lang="scss" scoped> </style>
Measured:
You can see that I turn on slow 3G to slow down the network speed, and then continuous requests will be cancelled
Detailed principle:
Principle source: Axios cancels outstanding asynchronous requests with cancelToken
It's convenient to view here. It's reprinted. Please jump to the link for details
The overall idea is that axios requests to create an XHR connection in the xhrAdapter, and then judge whether there is a canceltoken. If so, terminate the XHR connection (abort) through the processing of config.cancelToken.promise.then.
As a Promise, to trigger then, the Promise must be resolved, and the resolve should be triggered by the user in order to achieve the purpose. The user trigger is triggered by calling the cancle function.
Since each axios.get/post will generate a corresponding XHR connection, each connection needs to be generated by the source() factory function to generate a new token and the corresponding cancel. Otherwise, when calling cancel with the same token, several connections will be stopped together. This should be noted.
For browsers (different from node s), the asynchronous connection created by Axios essentially creates an XMLHttpRequest and wraps Promise.
// Axios > lib > adapters > xhr.js module.exports = function xhrAdapter(config) { return new Promise(function dispatchXhrRequest(resolve, reject) { var requestData = config.data; // ... var request = new XMLHttpRequest(); // ... if (config.cancelToken) { // If there is a cancletoken // Handle cancellation config.cancelToken.promise.then(function onCanceled(cancel) { if (!request) { return; } request.abort();// Cancel request reject(cancel); // Clean up request request = null; }); } // ... // Send the request request.send(requestData); } }
cancelToken.source is a factory function that returns a token instance and a cancel processing function.
// Axios > lib > cancel > cancelToken.js CancelToken.source = function source() { var cancel; var token = new CancelToken(function executor(c) { cancel = c; }); return { token: token, cancel: cancel }; };
It can be seen that the overall idea is to create an XHR connection in the xhrAdapter, and then terminate the XHR connection (abort) through the processing of config.cancelToken.promise.then.
As a Promise, to trigger then, the Promise must be resolved, and the resolve should be triggered by the user in order to achieve the purpose.
CancelToken is the constructor. The returned instance contains a promise.
// Axios > lib > cancel > CancelToken.js function CancelToken(executor) { // ... var resolvePromise; this.promise = new Promise(function promiseExecutor(resolve) { resolvePromise = resolve; }); var token = this; executor(function cancel(message) { if (token.reason) { // Cancellation has already been requested return; } token.reason = new Cancel(message); resolvePromise(token.reason); }); }
In the source() function, when new CancelToken is used, the executor function has only one operation: cancel = c.
Here, the executor(function cancel(...) {...})) actually assigns the entire function, that is, cancel = function cancel(...) {...}). Then, the cancel function, together with the CancelToken instance, is exposed to the user. That is, the corresponding cancel function must be executed on the CancelToken instance to take effect.
summary
So to sort out the execution process is:
|cancel -> resolvePromise -> then -> request.abort()
Since each axios.get/post will generate a corresponding XHR connection, each connection needs to be generated by the source() factory function to generate a new token and the corresponding cancel. Otherwise, when calling cancel with the same token, several connections will be stopped together. This should be noted.