What is Axios 🐎?
Axios is a concise, easy-to-use, and efficient code wrapper library for managing HTTP requests based on Promises. In layman’s terms, it’s a front-end alternative to Ajax, and you can use it to initiate HTTP requests. It’s based on Promises and can manage asynchronous operations better than Ajax callback functions. The source address
The main features of Axios
- Based on the Promise
- Supports browsers and Node.js environments
- You can add request, response interceptors, and transform request and response data
- Requests can be cancelled or interrupted
- Automatically convert JSON data
- The client supports XSRF defense
Source directory structure and main file function descriptionBased on version 0.21.4
| – /lib/ #
| | – /adapters/ # Defines the adapter that sends the request
| | | — HTTP. Js # node environment HTTP object
| | | — xhr.js # XML object for browser environment
| | – /cancel/ #
Helpers | | – /helpers/ #
| | – /core/ #
| | | — axios.js # Axios instance constructor
| | | — createError. Js # throw error
| | | — dispatchRequest.js # is used to call the HTTP request adapter method to send the request
| | | interceptorManager. js # interceptorManager. js
| | | — mergeConfig. Js # mergeConfig
# change the state of the Promise based on the HTTP response status
| | | — transformdata.js # transformData format
| | -axios.js # entry to create a constructor
| | -defaults. js #
| | -utils
From the entrance
We open up /lib/axios.js and start parsing from the entry.
var utils = require('./utils');
var bind = require('./helpers/bind');
var Axios = require('./core/Axios');
var mergeConfig = require('./core/mergeConfig');
var defaults = require('./defaults');
// The method to create an axios instance
function createInstance(defaultConfig) {
// Build a context object based on the default configuration, including the default configuration and request, corresponding interceptor object
var context = new Axios(defaultConfig);
// Creating bind returns a function and the context points to the context
var instance = bind(Axios.prototype.request, context);
// Copy prototype to instance similar to the method used in Axios prototype (e.g. Request, get, post...) Inheriting to the instance, this points to context
utils.extend(instance, Axios.prototype, context);
// Copy the context object properties (default configuration and request, corresponding interceptor object) to the instance. This points to context
utils.extend(instance, context);
// Create an axios instance, which should be used in axiOS encapsulation.
instance.create = function create(instanceConfig) {
// mergeConfig is used for deep merges
return createInstance(mergeConfig(defaultConfig, instanceConfig));
};
// Return the instance
return instance;
}
// Create instance defaulst with the default configuration
var axios = createInstance(defaults);
// Expose the Axios class for inheritance (I haven't used it yet)
axios.Axios = Axios;
// This throws an interrupt/cancel request related method into the entry object
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
// Make concurrent requests with full promise capabilities
axios.all = function all(promises) {
return Promise.all(promises);
};
// Use with axios.all to convert a single parameter group to multiple parameters =====> more details!!
axios.spread = require('./helpers/spread');
// used to monitor whether an error was thrown for Axios
axios.isAxiosError = require('./helpers/isAxiosError');
/ / export
module.exports = axios;
// Allow default export in TS
module.exports.default = axios;
Copy the code
createInstance
Through the analysis of the import files, we can find:
- We use it directly in normal development
axios.create()
Build instances and directaxios()
Both are passedcreateInstance
It’s constructed by this function.
This function does a few things:
- The first step is to build a context object based on the default configuration, including the default configuration and request, and corresponding interceptor objects
- The bind instance is created to return a letter, so we can use it axios(config) Use it this way, and the context points to the context
- Copying prototype to an instance is similar to copying Axios prototype methods (e.g. Request, get, POST…) Inherit to the instance and use it Axios. The get (), axios. Post () This refers to context
- Copy the context object properties (default configuration and request, corresponding interceptor object) to the instance, this pointing to context
- Return instance (instance as function)
axios.create
A PR update for the axios.create method was issued on September 5, 2021 to provide deep construction controller capability for large applications, or for multi-domain use of multiple instances, by encapsulating constructs against already constructed instances: See this PR for details
axios
Often usedapi
The request method is tagged with the following line of codeaxios
On the.
/lib/core/ axios.js
// Master request method All requests eventually point to this method
Axios.prototype.request = function request(config) {}// The following information will be explained
// Get the completed request URL method
Axios.prototype.getUri = function getUri(config) {};// Attach the normal request (no body data) to Prototype
utils.forEach(['delete'.'get'.'head'.'options'].function forEachMethodNoData(method) {
Axios.prototype[method] = function(url, config) {
// Both end up calling the request method
return this.request(mergeConfig(config || {}, {
method: method,
url: url,
data: (config || {}).data
}));
};
});
// Attach the request with body data to Prototype
utils.forEach(['post'.'put'.'patch'].function forEachMethodWithData(method) {
Axios.prototype[method] = function(url, data, config) {
// Both end up calling the request method
return this.request(mergeConfig(config || {}, {
method: method,
url: url,
data: data
}));
};
});
Copy the code
Axios.prototype has nine methods hanging on it, including the ones we use below.
axios.request(config)
axios.get(url[, config])
axios.delete(url[, config])
axios.head(url[, config])
axios.options(url[, config])
axios.post(url[, data[, config]])
axios.put(url[, data[, config]])
axios.patch(url[, data[, config]])
Copy the code
Request methods are divided into two types of traversal, which is attached to prototype because the last three methods may have request bodies and input parameter forms are different, so they should be handled separately.
Axios.prototype.request
Next we go to the core request method axios.prototype. request, which is the core skeleton of the whole Axios request, mainly doing adaptation to different config, and the key core chain call implementation. Let’s go into the code and see:
Axios.prototype.request = function request(config) {
Axios ('url',config)/axios(config)
if (typeof config === 'string') {
config = arguments[1) | | {}; config.url =arguments[0];
} else {
config = config || {};
}
// Merge the default configuration
config = mergeConfig(this.defaults, config);
// Convert the request method to lowercase
if (config.method) {
config.method = config.method.toLowerCase();
} else if (this.defaults.method) {
config.method = this.defaults.method.toLowerCase();
} else {
config.method = 'get';
}
var transitional = config.transitional;
if(transitional ! = =undefined) {
// Transitional configuration will be removed after version 1.0.0.
validator.assertOptions(transitional, {
silentJSONParsing: validators.transitional(validators.boolean, '1.0.0'),
forcedJSONParsing: validators.transitional(validators.boolean, '1.0.0'),
clarifyTimeoutError: validators.transitional(validators.boolean, '1.0.0')},false);
}
/ /... The following content has a relatively large update, separate out the detailed explanation !!!!
};
Copy the code
Promise chains form
Let’s take a look at the classic code that makes up the promise chain:
// Create an array of stored chained calls with the core call method dispatchRequest in the first place and empty second place
var chain = [dispatchRequest, undefined];
Resolve (config) because the request interceptor executes first, so setting the request interceptor gets all the config configuration for each request
var promise = Promise.resolve(config);
// Put the set request interceptor success handler and failure handler at the top of the array
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
// Put the success and failure handlers of the response interceptor at the end of the array
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
// The loop takes two at a time to form the promise chain
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
/ / return promise
return promise;
Copy the code
To make it clear, the entire promise chain can be interpreted as executing from left to right:
Request interceptor ===> Request === => Response interceptor
A new PR
Here’s a new PR 6 months ago that reconstructs the logic of this part of the code. It’s a big PR. Bear with me:
Here mainly for the asynchronous request interceptor is likely to happen, or a long macro task execution, and refactor the code before, because the request on the micro tasks performed, the timing of the micro task creation before build promise chain, if when performing macro task takes longer, before the request or an asynchronous request interceptor do, This causes a delay in the actual Ajax request being sent, so it is necessary to resolve this issue.
// Request interceptors store arrays
var requestInterceptorChain = [];
// All request interceptors are synchronous by default
var synchronousRequestInterceptors = true;
// Iterate over the array of registered request interceptors
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
// Where interceptor is registered on every interceptor object axios requests that the interceptor expose the runWhen configuration for interceptors that require runtime detection to execute
// If the function is configured and returns true, the result is logged to the interceptor chain, otherwise the layer loop is terminated
if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
return;
}
// interceptor.synchronous is an external configuration that identifies whether the interceptor is asynchronous or synchronous. The default is false(asynchronous)
If one of the request interceptors is asynchronous, then the following promise chain will be executed differently
synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
// into the request interceptor array
requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
});
// The corresponding interceptor stores an array
var responseInterceptorChain = [];
// Traversal is pushed sequentially into the interceptor storage array
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
var promise;
// If it is asynchronous, it is the default
if(! synchronousRequestInterceptors) {// This is the same logic as before the refactoring
var chain = [dispatchRequest, undefined];
// Request the interceptor plug to the front
Array.prototype.unshift.apply(chain, requestInterceptorChain);
// The response interceptor is plugged back
chain = chain.concat(responseInterceptorChain);
promise = Promise.resolve(config);
// Execute the loop
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
/ / return promise
return promise;
}
// Here is the synchronization logic
var newConfig = config;
// The request interceptor returns the latest config before the request one by one
while (requestInterceptorChain.length) {
var onFulfilled = requestInterceptorChain.shift();
var onRejected = requestInterceptorChain.shift();
// If an exception is caught, it is thrown directly
try {
newConfig = onFulfilled(newConfig);
} catch (error) {
onRejected(error);
break; }}// By not creating a microtask prematurely, the current macro task is too long, or there is an asynchronous task in one of the request interceptors that blocks true request latency
try {
promise = dispatchRequest(newConfig);
} catch (error) {
return Promise.reject(error);
}
// Response interceptor execution
while (responseInterceptorChain.length) {
promise = promise.then(responseInterceptorChain.shift(), responseInterceptorChain.shift());
}
return promise;
Copy the code
/core/InterceptorManager.js
// Interceptor adds two configuration parameters synchronous and runWhen
InterceptorManager.prototype.use = function use(fulfilled, rejected, options) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected,
// They are assumed to be asynchronous by default if your request interceptor is synchronous, you can default with this parameter, which tells AXIOS to run the code synchronously and avoid any delay in request execution.
synchronous: options ? options.synchronous : false.// If you want to execute a specific interceptor based on runtime checks, you can use the runWhen parameter, which is of type function
runWhen: options ? options.runWhen : null
});
return this.handlers.length - 1;
};
Copy the code
The above content needs to be combed repeatedly, the author is also combined with the source code and the discussion of the RECONSTRUCTION of the PR carried out a careful analysis: details see this PR!!
Specific change comparison diagram
Interceptor implementation
In our actual use of AXIos, request and corresponding interceptors are often used, which is one of the characteristics of AXIos. In the previous section, we analyzed the composition of the promise chain and when the interceptor was created, which we constructed in axios.create when createInstance went to new Axios instance. Directly on the code:
function Axios(instanceConfig) {
this.defaults = instanceConfig; The request and response interceptors created here are constructed from a common classthis.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
Copy the code
Let’s enter/core/InterceptorManager in js:
function InterceptorManager() {
this.handlers = [];
}
// Add interceptor add success or failure callback
InterceptorManager.prototype.use = function use(fulfilled, rejected, options) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected,
synchronous: options ? options.synchronous : false.runWhen: options ? options.runWhen : null
});
return this.handlers.length - 1;
};
// Unregister the specified interceptor
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null; }};// Iterate over the execution
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
// Make sure eject is not logged out before execution
if(h ! = =null) { fn(h); }}); };module.exports = InterceptorManager;
Copy the code
The implementation of interceptor is relatively simple. Through a unified model, a unified controller is constructed to manage the registration, deregistration and execution of interceptors.
dispatchRequest
Let’s go to the core request method dispatchRequest, where the structure looks relatively simple:
-
- Process the request header config configuration
- call
adapter
The adapter makes real requests, ajax requests for the browser environment and HTTP requests for the Node environment
-
- Construct the response data, which automatically transforms the JSON data
function dispatchRequest(config) {
// Cancel the request early
throwIfCancellationRequested(config);
// Assign a default value
config.headers = config.headers || {};
// Convert data
config.data = transformData.call(
config,
config.data,
config.headers,
config.transformRequest
);
// Merge the headers configuration
config.headers = utils.merge(
config.headers.common || {},
config.headers[config.method] || {},
config.headers
);
// Delete redundant merged data
utils.forEach(
['delete'.'get'.'head'.'post'.'put'.'patch'.'common'].function cleanHeaderConfig(method) {
deleteconfig.headers[method]; });// The adapter Axios supports both node side and browser side
var adapter = config.adapter || defaults.adapter;
// Execute the request
return adapter(config).then(function onAdapterResolution(response) {
// Cancel the request early
throwIfCancellationRequested(config);
// Do data conversion
response.data = transformData.call(
config,
response.data,
response.headers,
config.transformResponse
);
return response;
}, function onAdapterRejection(reason) {
if(! isCancel(reason)) { throwIfCancellationRequested(config);// Do data conversion
if(reason && reason.response) { reason.response.data = transformData.call( config, reason.response.data, reason.response.headers, config.transformResponse ); }}return Promise.reject(reason);
});
};
Copy the code
The adapter adapter
Classic design pattern: adaptor pattern application.
function getDefaultAdapter() {
var adapter;
// Check whether the XMLHttpRequest object exists to represent the browser environment
if (typeofXMLHttpRequest ! = ='undefined') {
// For browsers use XHR adapter
adapter = require('./adapters/xhr');
// The Node environment uses native HTTP to initiate requests
} else if (typeofprocess ! = ='undefined' && Object.prototype.toString.call(process) === '[object process]') {
adapter = require('./adapters/http');
}
return adapter;
}
Copy the code
./ Adapters /xhr.js encapsulates the native Ajax XMLHttpRequest object../ Adapters/HTTP. js encapsulates the Node HTTP module and handles HTTPS accordingly. Specific packaging details of various boundary details have done special processing, because we still use more in the browser daily, simple XHR packaging source code to do some overall.
Axios voluntarily cancels the request
How to use
Here’s how the next cancel request is used:
import { CancelToken } from axios;
// Source is an object with the structure {token, cancel}
// Token is used to indicate a request, which is a promise
// Cancel is a function that, when called, cancels the request for token injection
const source = CancelToken.source();
axios
.get('/user', {
// Inject the token into the request
cancelToken: source.token,
})
.catch(function (thrown) {
// Determine if this is due to active cancellation
if (axios.isCancel(thrown)) {
console.log('Voluntary cancellation', thrown.message);
} else {
console.error(thrown); }});// Call the cancel method here, which interrupts the request whether it returns successfully or not
source.cancel('I voluntarily cancel the request')
Copy the code
Source code analysis
In lib/axios.js, the axios instance throws three interfaces to cancel the request. Let’s look at three files involved in canceling the request.
Cancel.js: The Cancel function (fake class) accepts the argument message which is actually the argument in calling source.cancel() : Cancel message. The __CANCEL__ attribute on the prototype object identifies the message returned by the cancellation request as the message returned by the cancellation request
Canceltoken. js: CancelToken provides the ability to create a token instance to register a cancellation request and provides a cancellation request method
3. Iscancer.js: Used to determine whether it is the result returned for the cancellation request, i.e. whether it is an instance of Cancel
Let’s take a look at CancelToken’s source code, from an execution perspective:
1. The source method
// The token and cancel cancellation methods are exposed
CancelToken.source = function source() {
var cancel;
// Create an instance of CancelToken with two attributes: a promise and a Reason
// Call (cancel); // Call (cancel);
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel
};
};
Copy the code
The source method returns an object with two attributes: The token is an instance of new CancelToken, and cancel is an argument to the executor function of New CancelToken. It is a function used to call the active cancellation request when needed. Let’s examine CancelToken’s source code.
CancelToken constructor
function CancelToken(executor) {
// Type judgment
if (typeofexecutor ! = ='function') {
throw new TypeError('executor must be a function.');
}
// Create an instance of promise
var resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
This. Promise state will become a pity when the resolvePromise is implemented
resolvePromise = resolve;
});
/ / save this
var token = this;
// Cancel = c; // Cancel = c; // Cancel = c;
// This exposes the cancel function, leaving it up to the user to cancel and the user to execute the logic inside the function when calling cancel
executor(function cancel(message) {
// The request has been cancelled
if (token.reason) {
return;
}
Call new Cancel to construct an instance of Cancel information
token.reason = new Cancel(message);
// This. Promise instance state will be fulfilled, and the message will be Reason (new Cancel instance).
resolvePromise(token.reason);
});
}
Copy the code
A promise instance will be created in CancelToken, and a Reason will store the cancellation information. When the user calls source.cancel(message), the state of the promise instance will be changed to depressing, Create an instance of the Reason error message with the __CANCEL__ attribute, which identifies it as the message returned by the cancellation request.
3. How is the request handled!!
Operations within the Adapter
How do we interrupt/cancel the request when we call the cancel method? This piece of code in the adapter will find the desired answer. The source address
// Check whether the user has configured the cancellation token in the request modification
if (config.cancelToken) {
// If configured, then the promise on the instance is used to handle the logic when the cancel method is called
// If the Ajax request is sent before we register the.then for the cancelToken promise
// When we call the Cancel method, the promise of the cancelToken instance will become a pity state, and the logic in. Then will be fulfilled
config.cancelToken.promise.then(function onCanceled(cancel) {
if(! request) {return;
}
// Call native Abort to cancel the request
request.abort();
// The axios promise instance enters the Rejected state
reject(cancel);
// Request is null
request = null;
});
}
// The real request is sent at this point!!
request.send(requestData);
Copy the code
This is how AXIos interrupts the request. In other cases, it can cancel the request before and after the request is completed. This can also avoid unnecessary request sending and unnecessary logic execution. Let’s first look at the throwIfRequested method on the CancelToken prototype:
// There is a simple way to CancelToken by simply throwing reason
// Reason is an instance of the new Cancel argument that calls cancel
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
if (this.reason) {
throw this.reason; }};Copy the code
In our core request method dispatchRequest:
If you throw an error, the state of the axios constructed promise instance will be set to rejected, so you can go directly to. Catch
// Decide to throw if the request cancellation token is configured
function throwIfCancellationRequested(config) {
if (config.cancelToken) {
// Call the method that throws the errorconfig.cancelToken.throwIfRequested(); }}module.exports = function dispatchRequest(config) {
/ / request before
throwIfCancellationRequested(config);
/ /... Omit code
// The request is in the above adapter
return adapter(config).then(function onAdapterResolution(response) {
// After the request is complete
throwIfCancellationRequested(config);
/ /... Omit code
}, function onAdapterRejection(reason) {
// After the request is complete
if(! isCancel(reason)) { throwIfCancellationRequested(config);/ /... Omit code
}
return Promise.reject(reason);
});
};
Copy the code
We’re going to use the isCancel method in the AXIos request catch to determine if this exception is thrown by the Cancel request, that is, if it’s an instance of Cancel, and do something about it.
Let’s take a brief look at how requests are cancelled:
I believe that after a review of the above source analysis and flow chart analysis, should be able to cancel the principle of the request to have a rough understanding, the entire implementation process, details clear also need to read repeatedly.
Other small points
Concurrent ability
In the official axios, two methods axios.all and axios.spread are also provided, mainly for executing multiple concurrent requests, as follows:
function getUserAccount() {
return axios.get('/user/12345');
}
function getUserPermissions() {
return axios.get('/user/12345/permissions');
}
axios.all([getUserAccount(), getUserPermissions()])
.then(axios.spread((acct, perms) = > {
// The logic in the callback is executed only after both requests are completed
}));
Copy the code
Let’s look directly at the source code:
1. The axios.all method is exactly the same as the promise. all method, which is directly called promise. all
2. The axios.spread method takes a function as an argument that is also the response to all requests
// Make concurrent requests with full promise capabilities
axios.all = function all(promises) {
return Promise.all(promises);
};
// Accept a function callback
axios.spread = function spread(callback) {
// Return a new function, arr, which returns an array successfully
return function wrap(arr) {
// Return the result of the concurrent request to callback to facilitate the processing of the data returned by the concurrent request together as in the above example
return callback.apply(null, arr);
};
};
Copy the code
Tool function
The merge function is used to merge requests for config configuration information in a similar way to the deep copy deepClone function
function merge(/* obj1, obj2, obj3, ... * /) {
var result = {};
// Closures handle logical functions
function assignValue(val, key) {
// Result has this key value and is the same type of common Object recursive merge
if (isPlainObject(result[key]) && isPlainObject(val)) {
result[key] = merge(result[key], val);
// result does not have an assignment
} else if (isPlainObject(val)) {
result[key] = merge({}, val);
// Array type
} else if (isArray(val)) {
result[key] = val.slice();
// Other types are directly assigned
} else{ result[key] = val; }}// loop the call to the input parameter
for (var i = 0, l = arguments.length; i < l; i++) {
forEach(arguments[i], assignValue);
}
// Return the merged result
return result;
}
Copy the code
Extend function: inside AXIOS to attach some built-in properties and built-in methods to axiso instances
function extend(a, b, thisArg) {
// Loop b's properties hang on A
forEach(b, function assignValue(val, key) {
// If there is a specific this pointing to and the type is function
if (thisArg && typeof val === 'function') {
// Bind returns a function that uses apply internally
a[key] = bind(val, thisArg);
} else {
// Assign directlya[key] = val; }});return a;
}
Copy the code
conclusion
This article focus on the main source of the axios are at the 👆 steps one by one, if you read carefully believe there will be some good harvest, direct reading the source code is a hard way, hope that through a combination of the source code with this article, can help the readers better, faster to understand axios source, and later in the development of better use of axios.
Write in the last
If there are any errors in this article, please correct them in the comments section. If this article helped you, please like 👍 and follow ❤️