1, Use of axios
The basic use of axios has been covered in the previous article. Here is a brief review:
Send request
import axios from 'axios'; axios(config) // Direct incoming configuration axios(url[, config]) // Incoming url and configuration axios[method](url[, option]) // Directly call the request mode method, and pass in the url and configuration axios[method](url[, data[, option]]) // Directly call the request mode method and pass in data, url and configuration axios.request(option) // call request method const axiosInstance = axios.create(config) // axiosInstance Also have the above axios Ability of axios.all([axiosInstance1, axiosInstance2]).then(axios.spread(response1, response2)) // call all And incoming spread Callback
request interceptor
axios.interceptors.request.use(function (config) { // Write the code for pre-processing of sending request here return config; }, function (error) { // Write the code related to sending request error here return Promise.reject(error); });
Response interceptor
axios.interceptors.response.use(function (response) { // Write the post-processing code of response data here return response; }, function (error) { // The code for error response processing is written here return Promise.reject(error); });
Cancel request
// Mode 1 const CancelToken = axios.CancelToken; const source = CancelToken.source(); axios.get('xxxx', { cancelToken: source.token }) // Cancel request (request reason is optional) source.cancel('Active cancellation request'); // Mode II const CancelToken = axios.CancelToken; let cancel; axios.get('xxxx', { cancelToken: new CancelToken(function executor(c) { cancel = c; }) }); cancel('Active cancellation request');
2, Implement a simple version of axios
Build an Axios constructor, and the core code is request
class Axios { constructor() { } request(config) { return new Promise(resolve => { const {url = '', method = 'get', data = {}} = config; // Send ajax request const xhr = new XMLHttpRequest(); xhr.open(method, url, true); xhr.onload = function() { console.log(xhr.responseText) resolve(xhr.responseText); } xhr.send(data); }) } }
Export axios instance
// Finally, the axios method is exported, that is, the request method of the instance function CreateAxiosFn() { let axios = new Axios(); let req = axios.request.bind(axios); return req; } // Get the final global variable axios let axios = CreateAxiosFn();
The above can already implement the request of axios({})
The following is to implement the axios.method() request
// Define get,post... Methods and hang them on the Axios prototype const methodsArr = ['get', 'delete', 'head', 'options', 'put', 'patch', 'post']; methodsArr.forEach(met => { Axios.prototype[met] = function() { console.log('implement'+met+'method'); // Processing a single method if (['get', 'delete', 'head', 'options'].includes(met)) { // 2 parameters (url[, config]) return this.request({ method: met, url: arguments[0], ...arguments[1] || {} }) } else { // 3 parameters (url[,data[,config]]) return this.request({ method: met, url: arguments[0], data: arguments[1] || {}, ...arguments[2] || {} }) } } })
Move the methods on Axios.prototype to request
First, implement a tool class, mix the b method into a, and modify the point of this
const utils = { extend(a,b, context) { for(let key in b) { if (b.hasOwnProperty(key)) { if (typeof b[key] === 'function') { a[key] = b[key].bind(context); } else { a[key] = b[key] } } } } }
Modify export method
function CreateAxiosFn() { let axios = new Axios(); let req = axios.request.bind(axios); // Add code utils.extend(req, Axios.prototype, axios) return req; }
Constructor for building interceptors
class InterceptorsManage { constructor() { this.handlers = []; } use(fullfield, rejected) { this.handlers.push({ fullfield, rejected }) } }
Implement axios.interceptors.response.use and axios.interceptors.request.use
class Axios { constructor() { // New code this.interceptors = { request: new InterceptorsManage, response: new InterceptorsManage } } request(config) { ... } }
When executing the statements axios.interceptors.response.use and axios.interceptors.request.use, the implementation obtains the interceptors object on the Axios instance, then obtains the response or request interceptor, and then executes the use method of the corresponding interceptor
Move the methods and properties on Axios to request
function CreateAxiosFn() { let axios = new Axios(); let req = axios.request.bind(axios); // Mixing method, Handle the request method of axios and make it have get,post... Methods utils.extend(req, Axios.prototype, axios) // New code utils.extend(req, axios) return req; }
Now the request also has interceptors object. When sending the request, it will first obtain the handlers method of the request interceptor for execution
First, encapsulate the request to execute ajax into a method
request(config) { this.sendAjax(config) } sendAjax(config){ return new Promise(resolve => { const {url = '', method = 'get', data = {}} = config; // Send ajax request console.log(config); const xhr = new XMLHttpRequest(); xhr.open(method, url, true); xhr.onload = function() { console.log(xhr.responseText) resolve(xhr.responseText); }; xhr.send(data); }) }
Get callback in handlers
request(config) { // Interceptor and request assembly queue let chain = [this.sendAjax.bind(this), undefined] // Failed callbacks that appear in pairs will not be processed temporarily // Request interception this.interceptors.request.handlers.forEach(interceptor => { chain.unshift(interceptor.fullfield, interceptor.rejected) }) // Response interception this.interceptors.response.handlers.forEach(interceptor => { chain.push(interceptor.fullfield, interceptor.rejected) }) // Execute the queue, one pair at a time, and assign the latest value to promise let promise = Promise.resolve(config); while(chain.length > 0) { promise = promise.then(chain.shift(), chain.shift()) } return promise; }
chains are in the form of ['fullilled1 ',' reject1 ',' fullilled2 ',' reject2 ',' this. Sendajax ',' undefined ',' fullilled2 ',' reject2 ',' fullilled1 ',' reject1 ']
In this way, a simple version of axios can be successfully implemented
3, Source code analysis
First look at the directory structure
There are many implementation methods for sending requests from Axios. The implementation entry file is axios.js
function createInstance(defaultConfig) { var context = new Axios(defaultConfig); // Instance points to the request method, and the context points to context, so you can directly instance(option) Mode call // Axios.prototype.request The data type judgment of the first parameter enables us to instance(url, option) Mode call var instance = bind(Axios.prototype.request, context); // Extend the method on Axios.prototype to the instance object, // And specify the context as context, so that this will point to the context when executing the methods on the Axios prototype chain utils.extend(instance, Axios.prototype, context); // Copy context to instance // Extend its own attributes and methods on the context object to instance // Note: when the forEach method used inside extend traverses the object for in, it only traverses the attributes of the object itself, not the attributes on the prototype chain // In this way, instance has defaults, interceptors properties. utils.extend(instance, context); return instance; } // Create the default instance to be exported Create an axios instance generated by the default configuration var axios = createInstance(defaults); // Factory for creating new instances Extend the axios.create factory function, as well as the internal function createInstance 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;
The main core is Axios.prototype.request, the call implementation of various request modes is in request Internal implementation, a simple look request Logic of
Axios.prototype.request = function request(config) { // Allow for axios('example/url'[, config]) a la fetch API // judge config Whether the parameter is String, if yes, the first parameter is considered to be URL, the second parameter is the real config if (typeof config === 'string') { config = arguments[1] || {}; // hold url Place to config Object for later mergeConfig config.url = arguments[0]; } else { // If config Whether the parameter is String, the whole is treated as config config = config || {}; } // Merge the default configuration with the incoming configuration config = mergeConfig(this.defaults, config); // Set request method config.method = config.method ? config.method.toLowerCase() : 'get'; /* something... This part will be described separately in the following interceptors */ }; // stay Axios Mount on Prototype ' delete', ' get', ' head', ' options' The request method without parameters is also implemented internally request 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 })); }; }); // stay Axios Mount on Prototype ' post', ' put', ' patch' The request method of parameter transmission is also implemented internally request 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 })); }; });
The request entry parameter is config. It can be said that config implements the life of axios
axios Medium config is mainly distributed in these places:
-
Default configuration defaults.js
-
config.method defaults to get
-
call createInstance Method creation axios instance, incoming config
-
Direct or indirect call request Method, passed in config
// axios.js // Create an axios instance generated by the default configuration var axios = createInstance(defaults); // Extend the axios.create factory function, as well as the internal function createInstance 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 request method config.method = config.method ? config.method.toLowerCase() : 'get';
From the source code, you can see the priority: the default configuration object is default < method:get < Axios instance property this.default < request parameter
Let's focus on the request method
Axios.prototype.request = function request(config) { /* First mergeConfig ... Wait, don't elaborate */ // Hook up interceptors middleware Create interceptor chain dispatchRequest It is the top priority and the focus of follow-up var chain = [dispatchRequest, undefined]; // push each interceptor method Note: interceptor.fully or interceptor.rejected may be undefined this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { // Request interceptor reverse order Note here forEach Is the foreach method of a custom interceptor chain.unshift(interceptor.fulfilled, interceptor.rejected); }); this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { // Response interceptor sequence Note here forEach Is the foreach method of a custom interceptor chain.push(interceptor.fulfilled, interceptor.rejected); }); // Initialize a promise object with the status of resolved, and the received parameters are the config object that has been processed and merged var promise = Promise.resolve(config); // Chain of circular interceptors while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); // Every time the interceptor is ejected outward } // return promise return promise; };
interceptors are properties instantiated when building axios
function Axios(instanceConfig) { this.defaults = instanceConfig; this.interceptors = { request: new InterceptorManager(), // Request interception response: new InterceptorManager() // Response interception }; }
InterceptorManager constructor
// Interceptor initialization It's actually a set of hook functions function InterceptorManager() { this.handlers = []; } // When you call the use of the interceptor instance, you push 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; }; // The interceptor can be canceled. Set an interceptor method to null according to the ID returned when using // out-of-service splice perhaps slice The reason is After deletion id It will change, resulting in uncontrollable sequence or operation InterceptorManager.prototype.eject = function eject(id) { if (this.handlers[id]) { this.handlers[id] = null; } }; // This is where In the request method of Axios Method of interceptor in circulation forEach Loop execution hook function InterceptorManager.prototype.forEach = function forEach(fn) { utils.forEach(this.handlers, function forEachHandler(h) { if (h !== null) { fn(h); } }); }
The request interceptor method is unshift to the interceptor, and the response interceptor is push ed to the interceptor. Finally, they will be spliced with a method called dispatchRequest, which will be followed up promise Sequential execution
var utils = require('./../utils'); var transformData = require('./transformData'); var isCancel = require('../cancel/isCancel'); var defaults = require('../defaults'); var isAbsoluteURL = require('./../helpers/isAbsoluteURL'); var combineURLs = require('./../helpers/combineURLs'); // Judge whether the request has been canceled. If it has been canceled, throw the canceled function throwIfCancellationRequested(config) { if (config.cancelToken) { config.cancelToken.throwIfRequested(); } } module.exports = function dispatchRequest(config) { throwIfCancellationRequested(config); // If baseUrl is included, And it is not the absolute path of config.url. Combine baseUrl and config.url if (config.baseURL && !isAbsoluteURL(config.url)) { // Combine baseURL and url to form a complete request path config.url = combineURLs(config.baseURL, config.url); } config.headers = config.headers || {}; // Use the transformRequest method in / lib/defaults.js to format config.headers and config.data // For example, Accept and content type in headers are uniformly capitalized // For example, if the request body is an Object, it will be formatted as a JSON string and add application / JSON; Content type of charset = UTF-8 // And a series of operations config.data = transformData( config.data, config.headers, config.transformRequest ); // Merge headers with different configurations. The configuration priority of config.headers is higher config.headers = utils.merge( config.headers.common || {}, config.headers[config.method] || {}, config.headers || {} ); // Delete the method attribute in headers utils.forEach( ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'], function cleanHeaderConfig(method) { delete config.headers[method]; } ); // If adapter is configured in config, use the default request method of configuring adapter in config instead var adapter = config.adapter || defaults.adapter; // Use the adapter method to initiate the request (the adapter varies according to the browser environment or Node environment) return adapter(config).then( // Request a callback that returns correctly function onAdapterResolution(response) { // Judge whether or not the request has been canceled. If the request has been canceled, throw it to cancel throwIfCancellationRequested(config); // Use the transformResponse method in / lib/defaults.js to format the data returned by the server // For example, use JSON.parse to parse the response body response.data = transformData( response.data, response.headers, config.transformResponse ); return response; }, // Request failed callback function onAdapterRejection(reason) { if (!isCancel(reason)) { throwIfCancellationRequested(config); if (reason && reason.response) { reason.response.data = transformData( reason.response.data, reason.response.headers, config.transformResponse ); } } return Promise.reject(reason); } ); };
Let's take a look at how axios implements the cancellation request. The implementation file is in CancelToken.js
function CancelToken(executor) { if (typeof executor !== 'function') { throw new TypeError('executor must be a function.'); } // stay CancelToken Define a pending Stateful promise , take resolve The callback is assigned to an external variable resolvePromise var resolvePromise; this.promise = new Promise(function promiseExecutor(resolve) { resolvePromise = resolve; }); var token = this; // Execute now The passed in executor function passes the real cancel method through parameters. // Once called, the resolvePromise is executed, that is, the resolve of the previous promise, and the state of the promise is changed to resolve. // Then, as defined in xhr The CancelToken.promise.then method will execute, This xhr internally cancels the request executor(function cancel(message) { // Judge whether the request has been cancelled to avoid multiple executions if (token.reason) { return; } token.reason = new Cancel(message); resolvePromise(token.reason); }); } CancelToken.source = function source() { // source Method returns a CancelToken Examples, and direct use new CancelToken It's the same operation var cancel; var token = new CancelToken(function executor(c) { cancel = c; }); // Return created CancelToken Instance and cancellation method return { token: token, cancel: cancel }; };
In fact, the operation of canceling the request is xhr.js There is also a response in the cooperation
if (config.cancelToken) { config.cancelToken.promise.then(function onCanceled(cancel) { if (!request) { return; } // Cancel request request.abort(); reject(cancel); }); }
The clever place is In CancelToken executor Function to control the state of promise through the transfer and execution of resolve function