Axios
Promise based HTTP client for the browser and Node.js – Promise based HTTP library that can be used in browsers and NodeJs
Characteristics (Ability)
- XMLHttpRequests can be created from the browser
- Support for creating HTTP requests from NodeJs
- Supporting Promise API
- Intercept requests and responses
- Transform request data and response data, automatically transform JSON data
- Cancel the request
- The client supports XSRF defense
Axios is so widely used today that front-end developers often use it to make AJAX requests and to do front-end and back-end data interactions. So this article focuses on the axiOS implementation logic on the browser side, including
- Implementation of multiple invocation methods
- Implementation of multiple pass configurations
- Implementation of interceptors
- The implementation of converting request data and response data and automatically converting JSON data
- Cancel the implementation of the request
The directory structure
How to implement multiple invocation methods
If you use Axios a lot, you should know how to use axios. For example:
import axios from 'axios';
axios(config) // Pass in the configuration directly
axios(url[, config]) // Pass in the URL and configuration
axios[method](url[, option]) // Call the request method directly, passing in the URL and configuration
axios[method](url[, data[, option]]) // Call the request method directly, passing in data, URL, and configuration
axios.request(option) // Call the request method
const axiosInstance = axios.create(config)
// axiosInstance also has the above axios capabilities
axios.all([axiosInstance1, axiosInstance2]).then(axios.spread(response1, response2))
// Call all and pass the spread callback
Copy the code
For many of these uses, the internals of AXIos are actually represented in axios.js, a file that exposes the interface
function createInstance(defaultConfig) {
var context = new Axios(defaultConfig);
// Instance refers to the Request method, and the context refers to the context, so it can be called directly with instance(option)
// axios.prototype. request specifies the datatype of the first parameter, which allows us to call instance(URL, option)
var instance = bind(Axios.prototype.request, context);
// Copy axios.prototype to instance
// Extend the axios.prototype method to instance,
// Specify the context as context so that when the method on the Axios prototype chain is executed, this points to the context
utils.extend(instance, Axios.prototype, context);
// Copy context to instance
// Extend the context object's own properties and methods to instance
// Note: Because the forEach method that extends internally does for in traversal of the object, it traverses only properties of the object itself, not properties on the prototype chain
// In this case, instance has the defaults and interceptors attributes.
utils.extend(instance, context);
return instance;
}
// Create the default instance to be exported creates an AXIOS instance generated by the default configuration
var axios = createInstance(defaults);
// Factory for creating new Instances extends the axios.create Factory function, which is also createInstance inside
axios.create = function create(instanceConfig) {
return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
// Expose all/spread
axios.all = function all(promises) {
return Promise.all(promises);
};
axios.spread = function spread(callback) {
return function wrap(arr) {
return callback.apply(null, arr);
};
};
module.exports = axios;
Copy the code
The main core is axios.prototype.request. All kinds of request invocation are realized inside request. Let’s take a look at the logic of request
Axios.prototype.request = function request(config) {
// Allow for axios('example/url'[, config]) a la fetch API
// Determine if the config argument is a string. If so, the first argument is a URL and the second argument is a real config
if (typeof config === 'string') {
config = arguments[1) | | {};// Place the URL in the config object for later mergeConfig
config.url = arguments[0];
} else {
// If the config argument is not a string, the whole thing is treated as config
config = config || {};
}
// Merge the default configuration with the incoming configuration
config = mergeConfig(this.defaults, config);
// Set the request method
config.method = config.method ? config.method.toLowerCase() : 'get';
/* something... This section will be covered separately in future interceptors
};
// Mount request methods 'delete', 'get', 'head', 'options' with no arguments on the Axios prototype
utils.forEach(['delete'.'get'.'head'.'options'].function forEachMethodNoData(method) {
Axios.prototype[method] = function(url, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url
}));
};
});
// Mount the request methods' POST ', 'put', 'patch' and pass parameters on the Axios prototype, which is also request inside the implementation
utils.forEach(['post'.'put'.'patch'].function forEachMethodWithData(method) {
Axios.prototype[method] = function(url, data, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url,
data: data
}));
};
});
Copy the code
Start with the config configuration parameters
The config configuration should run through the “life” of Axios, from createInstance in the entry axios.js to the actual request method axios.prototype.request, so we’ll just go with it for now
Config in Axios is mainly distributed in these places:
- The default configuration
defaults.js
- The config. Method by default
get
- call
createInstance
Method to create an axios instance, passed in config - Directly or indirectly called
request
Method, passed in config
The priority relationships between these four areas are as follows:
Embodied in the source code:
// axios.js
// Create an axiOS instance generated by the default configuration
var axios = createInstance(defaults);
// Extend axios.create factory function, also createInstance inside
axios.create = function create(instanceConfig) {
return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
// Axios.js
// Merge the default configuration with the incoming configuration
config = mergeConfig(this.defaults, config);
// Set the request method
config.method = config.method ? config.method.toLowerCase() : 'get';
Copy the code
A request for a real request
The axios.prototype. request method, one of axios’s core methods, was introduced in the axios usage method and config flow configuration section
Axios.prototype.request = function request(config) {
/* mergeConfig... Etc., no longer elaborate */
// Hook up Interceptors Middleware creates a chain of interceptors
var chain = [dispatchRequest, undefined];
// This is a big pity. // This is a big pity. // This is a big pity
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
Note that forEach here is the forEach method of the custom interceptor
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
Note that forEach here is the forEach method of the custom interceptor
chain.push(interceptor.fulfilled, interceptor.rejected);
});
// Initialize a Promise object in the resolved state and receive the config object that has been processed and merged
var promise = Promise.resolve(config);
// Loop the chain of interceptors
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift()); // Eject interceptor every time
}
/ / return promise
return promise;
};
Copy the code
Maybe this code looks a little confusing because it uses promises, interceptors. So let’s start with promise, which is a JavaScript solution to the purgatory of asynchronous callbacks. Then there are interceptors, which are the ———— interceptors for intercepting requests and responses mentioned earlier in this article.
The interceptor
The purpose of the interceptor is to reprocess the requested config before we make the request and intercept the requested result before we get the result. The official timing is then or before catch.
This.interceptors is an attribute on the axios instance. It is initialized in axios.js and has two attributes: request and response.
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(), // Request interception
response: new InterceptorManager() // Response interception
};
}
Copy the code
It’s defined in interceptorManager.js, and they both have a use method. The use method takes two arguments, the first of which is similar to Promise’s resolve function and the second to Promise’s Reject function. We can execute synchronous or asynchronous code logic in resolve and reject.
// The initialization of an interceptor is essentially a set of hook functions
function InterceptorManager() {
this.handlers = [];
}
// Calling the use of an interceptor instance pushes the method into the hook function
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length - 1;
};
// Interceptor methods can be nulled based on the ID returned by the use method
// The reason why we can't use splice or slice is that the id will change after deleting, resulting in uncontrollable order or operation
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null; }};// This is where the loop interceptor's method forEach loop executes the hook function in the Axios request method
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if(h ! = =null) { fn(h); }}); };Copy the code
Now that you know what an interceptor is, it’s time to go back to Request and see how it works. Previously for interceptors, we knew that the request interceptor method was unshift into the interceptor and the response interceptor was push into the interceptor. Eventually they will concatenate a method called dispatchRequest to be executed sequentially by subsequent promises.
The result of the splicing is a chain like this:
Request interceptor2Resolve, request interceptor2Reject, request interceptor1Resolve, request interceptor1Reject, dispatchRequest,undefined, response interceptor1Resolve, the response interceptor1Reject, which responds to interceptors2Resolve, the response interceptor2Reject,]Copy the code
So interceptors are executed in chain order. In the case of request interceptors, later-added interceptors are executed during the pre-request process; For response interceptors, the interceptor added first is executed after the response.
After constructing the PromiseChain, we see the interceptor handlers on both sides and a dispatchRequest in the middle, which is where Axios actually initiates a request, as we’ll see in more detail.
Then define a promise that has resolved config, loop through the chain, take each interceptor object, and add their Resolved and Rejected functions to the promise.then argument. This is equivalent to the chain invocation of a Promise, which ultimately returns a Promise, implementing the effect of layer upon layer of chain invocation of the interceptor.
This concludes the first half of the section and the analysis of dispatchRequest and ancillary functions such as data conversion and cancellation request will follow. Axios source code