This is the fourth day of my participation in the November Gwen Challenge. See details: The last Gwen Challenge 2021.
Axios introduction
Axios is a Promise-based HTTP library that can be used in browsers and Node.js and supports the following features:
- Create XMLHttpRequests from the browser
- Create HTTP requests from Node.js
- Supporting Promise API
- Intercept requests and responses
- Transform request and response data
- Cancel the request
- Automatic conversion
JSON
data - The client supports XSRF defense
Related documents: NPM GitHub
Source directory
├ ─ ─ / lib /// Project source code└ ─ ─ / adapters// Define the adapter that sends the request├ ─ ─ HTTP. Js// Node environment HTTP object├ ─ ─ XHR. Js// The browser environment XML object└ ─ ─ / cancel /// Define the cancel request function└ ─ ─ / helpers// Auxiliary methods└ ─ ─ / core /// Core functions├ ─ ─ Axios. Js// The axios instance constructor├ ─ ─ createError. Js// Throw an error├ ─ ─ dispatchRequest. Js// Call the HTTP request adapter method to send the request├ ─ ─ InterceptorManager. Js// Interceptor manager├ ─ ─ mergeConfig. Js// Merge parameters├ ─ ─ settle. Js// Change the state of the Promise based on the HTTP response status├ ─ ─ transformData. Js// Convert to data format└ ─ ─ axios. Js// create a constructor└ ─ ─ defaults. Js// Default configuration└ ─ ─ utils. Js// Public utility functions
Copy the code
(1) Start from the entrance
Axios provides a function createInstance to assist in creating an instance of the Axios class. Note, however, that instead of returning an Axios instance object, this function returns the instance object’s Request method, and mounts the instance object’s other alias methods to the Request method (functions are also objects, and attribute methods can be added). Hence the following usage:
axios({... }); axios.get('/', {... })...Copy the code
// The method to create an axios instance
function createInstance(defaultConfig) {
// Generate an axiOS instance according to the default configuration
var context = new Axios(defaultConfig);
// Create an instance and return request, where this refers to context
var instance = bind(Axios.prototype.request, context);
// Inherits the Axios prototype method from instance. The inner bind makes this point to context
utils.extend(instance, Axios.prototype, context);
// Copy context object properties (default configuration and request, corresponding interceptor object) to the instance
utils.extend(instance, context);
// The factory function that creates the instance should be used in axiOS packaging (we put some default, common configuration on the instance, reuse the instance, no need to recreate the instance each time).
instance.create = function create(instanceConfig) {
return createInstance(mergeConfig(defaultConfig, instanceConfig));
};
// Return the instance object
return instance;
}
// Create an instance
var axios = createInstance(defaults);
// Also provides some other methods for the exported axios:
// Mount the original Axios class, which can be used for inheritance
axios.Axios = Axios;
// Interrupt/cancel the request
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
axios.VERSION = require('./env/data').version;
// Concurrent requests, full promise
axios.all = function all(promises) {
return Promise.all(promises);
};
axios.spread = require('./helpers/spread');
// used to monitor whether an error was thrown for Axios
axios.isAxiosError = require('./helpers/isAxiosError');
/ / export
module.exports = axios;
// Allows default exports in TypeScript
module.exports.default = axios;
Copy the code
When we reference the Axios library, it internally calls createInstance to initialize and return Request
We can see that we normally use axios() and axios.create() to create instances by calling createInstance.
So, we can create another AXIos request using the factory function:
// Use the default request
axios.get('/user');
// Use the new configuration to send the request
let newRequest = axios.create({baseURL: 'http://localhost:9999'});
newRequest.get('/user');
Copy the code
(2)Axios class
The Axios class is the core class that encapsulates and provides the API used by the request.
//Axios.js
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
/** * send a request **@param {Object} config The config specific for this request (merged with this.defaults)
*/
Axios.prototype.request = function request(config) {
// Allow for axios('example/url'[, config]) a la fetch API
if (typeof config === 'string') {
config = arguments[1) | | {}; config.url =arguments[0];
} else {
config = config || {};
}
// Merge the configuration
config = mergeConfig(this.defaults, config);
/ / set the config. Method
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) {
validator.assertOptions(transitional, {
silentJSONParsing: validators.transitional(validators.boolean),
forcedJSONParsing: validators.transitional(validators.boolean),
clarifyTimeoutError: validators.transitional(validators.boolean)
}, false);
}
// The key chain call, which is specifically resolved at.... later in the article
};
Copy the code
Axios also provides a list of HTTP method alias functions based on the Request method and mounts them to the prototype:
// Provide aliases for supported request methods
// Encapsulate requests that do not need to submit body data
utils.forEach(['delete'.'get'.'head'.'options'].function forEachMethodNoData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, config) {
return this.request(mergeConfig(config || {}, {
method: method,
url: url,
data: (config || {}).data
}));
};
});
// Handle requests that can submit body data
utils.forEach(['post'.'put'.'patch'].function forEachMethodWithData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, data, config) {
return this.request(mergeConfig(config || {}, {
method: method,
url: url,
data: data
}));
};
});
Copy the code
(3) Configuration processing
There are three configuration points in Axios:
- Request method configuration
- The instance configuration
- Global configuration
Reference: axios-http.com/zh/docs/req…
(1) Request method configuration
Is the configuration passed in request and alias methods such as GET and POST
axios({
url: '/user'
});
axios.get('/user', {
params: {
page:1.limit:2}})...Copy the code
(2)Instantiate configuration
We can also pass in the base configuration at instantiation time (we can pass in some of the requested common configurations at instantiation time)
let newRequest = axios.create({
baseURL: 'http://localhost:9999'
});
Copy the code
(3)Global (default) configuration
Axios also has a set of default configuration items, which are used if none are passed in at instantiation time or if the instantiation axios exports by default.
// The default configuration can be obtained via axios.defaults
axios.defaults.baseURL = 'http://localhost:8888';
axios.get('/user');
Copy the code
Configuring a Priority
Request Configuration > Instance Configuration > Default Configuration
(4) Application and implementation of interceptors
There is a middleware-like mechanism in AXIos to handle tasks before the Request method requests them and after the response (before the user code executes).
// Add request interceptor
axios.interceptors.request.use(function (config) {
// What to do before sending the request
return config;
}, function (error) {
// What to do about the request error
return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
// all status codes in the 2xx range trigger this function.
// What to do with the response data
return response;
}, function (error) {
// Any status code outside the 2xx range triggers this function.
// Do something about the response error
return Promise.reject(error);
});
Copy the code
Reference: axios-http.com/zh/docs/int…
Interceptor implementation
The implementation of interceptors is relatively simple, essentially resembling middleware arrays with two groups: requests and responses. Through the unified model, a unified controller is constructed to manage the registration, logout, and execution of interceptors.
// Axios.js
function Axios(instanceConfig) {
this.defaults = instanceConfig;
// The request and response interceptors created here are constructed from a unified class
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
// core/InterceptorManager.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,
// 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;
};
// 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
How interceptors work
// Axios.js
Axios.prototype.request = function request(config) {
// Key chain calls
// Chain call storage array -- request array
var requestInterceptorChain = [];
// All request interceptors are synchronous by default
var synchronousRequestInterceptors = true;
// Iterate over the array of registered interceptors
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
// Here interceptor is each interceptor object registered
// The AXIos request interceptor exposes the runWhen configuration for interceptors that require runtime detection to execute
// For example, implement an interceptor that only gets
//
// function onGetCall(config) {
// return config.method === 'get';
// }
// axios.interceptors.request.use(function (config) {
// config.headers.test = 'special get headers';
// return config;
// }, null, { runWhen: onGetCall });
//
// If the function is configured and returns true, it is logged to the interceptor chain
// If not, end the loop
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;
// This is a big surprise when the interceptor. This is a big surprise when the interceptor
requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
});
// Chain call storage array -- response array
var responseInterceptorChain = [];
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
This is a big pity. Interceptor. Rejected joins the end of the chain
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
var promise;
// If it is asynchronous, it is the default
if(! synchronousRequestInterceptors) {// The dispatchRequest method is explained in the adapter section
var chain = [dispatchRequest, undefined];
// Request the interceptor plug to the front
Array.prototype.unshift.apply(chain, requestInterceptorChain);
// The response interceptor is plugged back
chain.concat(responseInterceptorChain);
//chain = [fulfilled[1],rejected[1],fulfilled[0],rejected[0],dispatchRequest, undefined,fulfilled[3],rejected[3],fulfilled[4],rejected[4]]
promise = Promise.resolve(config);
while (chain.length) {
// (depressing, Rejected) One group of two each time
// shift: Removes the first element from the array and returns the value of that element. This method changes the length of the array.
// Each time the promise is reassigned
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
}
// Here is the synchronization logic
var newConfig = config;
// Request interceptors to go one by one
while (requestInterceptorChain.length) {
var onFulfilled = requestInterceptorChain.shift();
var onRejected = requestInterceptorChain.shift();
try {
// Get the latest config every time
newConfig = onFulfilled(newConfig);
} catch (error) {
onRejected(error);
break; }}// The microtask will not be created prematurely by this point
// This solves the problem of premature creation of microtasks, the current macro task being too long, or an asynchronous task in a request interceptor blocking 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
Interceptor application
// Add request interceptor
axios.interceptors.request.use(function (config) {
/ / send the token
try {
let token = localStorage.getItem('token');
config.headers.authorization = token;
} catch(e){}
return config;
}, function (error) {
return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
console.log('Request Log', response);
return response;
}, function (error) {
console.log('Request Log', response);
alert('Wrong');
return Promise.reject(error);
});
Copy the code
(5) Adapter
In the browser, we use the API provided by XMLHttpRequest to send the request. In node.js, we need to use the API provided by the HTTP module to send the request. The underlying API structure, including the formatting of the response data, is also inconsistent. Axios, in order to address this discrepancy and allow users to use the same API in different environments, uses adaptation mode.
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;
}
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
./ Adapters /xhr.js is a wrapper for the native Ajax XMLHttpRequest object.
./ Adapters/HTTP. js encapsulates the Node HTTP module and handles it accordingly for HTTPS.
(6) Write at the end
If there are any errors in this article, please correct them in the comments section. If this article helped you, please like 👍 and follow ❤️
Content Reference (thanks) :
Lot – Axios warehouse
The most complete and detailed reading of the Axios source code – this is enough
This article continues to update ~ welcome to follow my nuggets and Github ~