Axios is an excellent HTTP request library based on Promises. This is very depressing. Once a Promise is fulfilled, it cannot be finished. The state can only change from pending -> fulfilled or pending -> rejected. Here are two ways to undo a request using Axios.
- You can create a Cancel token using the CancelToken. Source factory method;
var CancelToken = axios.CancelToken; var source = CancelToken.source(); axios.get('/user/12345', { cancelToken: source.token }).catch((err) => { if(axios.isCancel(err)) { console.log('Request canceled', err.message); } else {// handle error}}); // Cancel the request (the message argument is optional) source.cancel('Operation canceled by the user.');Copy the code
- You can commonly cancel a token by passing an executor function to the CancelToken constructor;
var CancelToken = axios.CancelToken; var cancel; Get ('/user/1234', {cancelToken: new cancelToken ((c)) => {// executor accepts a function of cancel as argument cancel = c; }); });Copy the code
The above two methods are essentially the same, except that the CancelToken source method encapsulates the second method. The diagram below shows the structure of the Axios source code, where the red line is the core code that axios uses to do undo requests. Let’s unravel the mystery step by step.
- Cancel.js
The code for cancel.js is very simple, defining a constructor, adding a method toString to the prototype to print some messages, and adding a property __CANCEL__ to the prototype and setting its value to true.
'use strict';
/**
* A `Cancel` is an object that is thrown when an operation is canceled.
*
* @class
* @param {string=} message The message.
*/
function Cancel(message) {
this.message = message;
}
Cancel.prototype.toString = function toString() {
return 'Cancel' + (this.message ? ': ' + this.message : '');
};
Cancel.prototype.__CANCEL__ = true;
module.exports = Cancel;
Copy the code
- isCancel.js
This is used to determine whether the request has been revoked.
'use strict'; module.exports = function isCancel(value) { return !! (value && value.__CANCEL__); };Copy the code
- CancelToken.js
CancelToken is a constructor that takes an executor argument of type function or raises an exception. Next, add the Promise attribute to the object constructed by the cancelToken (note: this is the key bridge to the cancellation request). Finally, the executor function is executed. The cancel function first determines whether the request has already been cancelled, then adds an instance with the Reason attribute cancel to the token, and finally calls the resolvePromise. This. Promise will be fulfilled gradually from pending -> fulfilled. ThrowIfRequested is a method added to the CancelToken stereotype that determines whether the current object has a reason attribute and throws an exception if it does. Source is a method added to the CancelToken function (which is also an object) that returns an object with token and cancel properties. Token is an example of new CancelToken, and cancel is the parameter C of the executor function. What’s the magic of this parameter C?
'use strict';
var Cancel = require('./Cancel');
/**
* A `CancelToken` is an object that can be used to request cancellation of an operation.
*
* @class
* @param {Function} executor The executor function.
*/
function CancelToken(executor) {
if (typeof executor !== '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) {
if (token.reason) {
// Cancellation has already been requested
return;
}
token.reason = new Cancel(message);
resolvePromise(token.reason);
});
}
/**
* Throws a `Cancel` if cancellation has been requested.
*/
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
if (this.reason) {
throw this.reason;
}
};
/**
* Returns an object that contains a new `CancelToken` and a function that, when called,
* cancels the `CancelToken`.
*/
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel
};
};
module.exports = CancelToken;
Copy the code
Most of the code has already been posted, but here’s an example to tease out the process. The author uses creation-react-app to build a simple React project with two buttons, one for send request and one for undo request. After clicking the “send” request, the back end needs to process for 5s before giving the response; Click undo request and the request is revoked.
First, we click the send request button and execute the following code.
const sendRequest = () => { axios.get('/api', { cancelToken: new CancelToken(function(c) { cancel = c; }) }).then((res) => { console.log('res: ', res); })};Copy the code
The source code first uses the default created instance, calling the get method. The get method actually calls the request method, and in the body of the request method, you send the request by calling the dispatchRequest method.
// axios.js function createInstance(defaultConfig) { var context = new Axios(defaultConfig); var instance = bind(Axios.prototype.request, context); // Copy axios.prototype to instance utils.extend(instance, Axios.prototype, context); // Copy context to instance utils.extend(instance, context); return instance; } // Create the default instance to be exported var axios = createInstance(defaults); module.exports = axios; // Axios.js utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) { /*eslint func-names:0*/ Axios.prototype[method] = function(url, Config) {/ / this. Request call Axios. Prototype. The request method to return this. The request (mergeConfig (config | | {}, {method: method, url: url, data: (config || {}).data })); }; }); var dispatchRequest = require('./dispatchRequest'); /** * Dispatch a request * * @param {Object} config The config specific for this request (merged with this.defaults) */ Axios.prototype.request = function request(config) {Hook up interceptors middleware var chain = [dispatchRequest, undefined]; var promise = Promise.resolve(config); while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); } return promise; };Copy the code
Take a look at the source code for DispatchRequest.js and see what it does. Js uses getDefaultAdapter to get the default adapter. GetDefaultAdapter is used to send requests to different objects depending on the environment. (In this case, XMLHttpRequest)
// dispatchRequest.js 'use strict'; var utils = require('./.. /utils'); var transformData = require('./transformData'); var isCancel = require('.. /cancel/isCancel'); var defaults = require('.. /defaults'); /** * Dispatch a request to the server using the configured adapter. * * @param {object} config The config that is to be used for the request * @returns {Promise} The Promise to be fulfilled */ module.exports = function DispatchRequest (config) {/ / omitted var adapter = config. Adapter | | defaults. The adapter. Return adapter(config).then(function onAdapterResolution(response) {// omit}, Function onAdapterRejection(reason) {// reject}); }; // defaults.js function getDefaultAdapter() { var adapter; if (typeof XMLHttpRequest ! == 'undefined') { // For browsers use XHR adapter adapter = require('./adapters/xhr'); } else if (typeof process ! == 'undefined' && Object.prototype.toString.call(process) === '[object process]') { // For node use HTTP adapter adapter = require('./adapters/http'); } return adapter; }Copy the code
Next look at the source code in the./ Adapters /xhr.js file. Most of the source code is fairly straightforward, starting with the new XMLHttpRequest object and then binding the callback function to the event. But there is a cancelToken code, where we see request.abort(), which is used to cancel requests.
if (config.cancelToken) { // Handle cancellation config.cancelToken.promise.then(function onCanceled(cancel) { if (! request) { return; } request.abort(); reject(cancel); // Clean up request request = null; }); }Copy the code
The source code for the undo request is already listed. As I said, clicking the send request button, the server has to process 5s before it can respond. Within 5s, click the undo request button.
const abortRequest = () => {
cancel();
};
Copy the code
The cancel method essentially calls the parameter cancel of the executor method under canceltoken.js. This will be fulfilled gradually. This will be fulfilled gradually. This will be fulfilled gradually.
// CancelToken.js
var resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
executor(function cancel(message) {
if (token.reason) {
// Cancellation has already been requested
return;
}
token.reason = new Cancel(message);
resolvePromise(token.reason);
});
Copy the code
Now that the state of this. Promise has changed, the then for the corresponding chunk should also be executed. In xhr.js, the request.abort method is executed and the request. onAbort callback is triggered. Call Reject (createError(‘Request aborted’, config, ‘ECONNABORTED’, Request)).
// xhr.js // Handle browser request cancellation (as opposed to a manual cancellation) request.onabort = function handleAbort() { if (! request) { return; } console.log('aborted... '); reject(createError('Request aborted', config, 'ECONNABORTED', request)); // Clean up request request = null; }; if (config.cancelToken) { // Handle cancellation config.cancelToken.promise.then(function onCanceled(cancel) { if (! request) { return; } request.abort(); // Since the onAbort callback calls the reject method, the state is not changed. Reject (cancel) is ignored. // Clean up request request = null; }); }Copy the code
After the reject method is called, a promise example changes state and the corresponding callback function should be executed. Execute the second argument of the then function. IsCancel determines whether reason is an instance of Cancel. In the above analysis, we can find that reason is an error created by createError, so! IsCancel to true, call throwIfCancellationRequested method. It calls the cancelToken’s throwIfRequested method, throwing an exception.
// dispatchRequest.js 'use strict'; var utils = require('./.. /utils'); var transformData = require('./transformData'); var isCancel = require('.. /cancel/isCancel'); var defaults = require('.. /defaults'); /** * Throws a `Cancel` if cancellation has been requested. */ function throwIfCancellationRequested(config) { if (config.cancelToken) { config.cancelToken.throwIfRequested(); } } /** * Dispatch a request to the server using the configured adapter. * * @param {object} config The config that is to be used for the request * @returns {Promise} The Promise to be fulfilled */ module.exports = function dispatchRequest(config) { throwIfCancellationRequested(config); // Omit some code... var adapter = config.adapter || defaults.adapter; Return adapter(config).then(function onAdapterResolution(response) {// omit}, function onAdapterRejection(reason) { if (! isCancel(reason)) { throwIfCancellationRequested(config); // Transform response data if (reason && reason.response) { reason.response.data = transformData( reason.response.data, reason.response.headers, config.transformResponse ); } } return Promise.reject(reason); }); };Copy the code
- The flow chart