Source directory structure
# 📁 lib # |-Request method used primarily by adapters // axios # Request functions used by the node side in |-|-http.js // Axios # Request functions used on browser side in |-|-xhr.js // Axios # |- 📁 cancel # |-|-|-Cancel.js //Defined, cancel the information structure returned by the request # |-|-|-CancelToken.js//Defines the main method used to cancel the request # |-|-|-isCancel.js //Information to determine whether the request was cancelled # |- 📁 core # |-|-|-Axios.js // Axios class # |-|-|-dispatchRequest.js//Where the request originated # |--|-InterceptorManager.js // InterceptorManager class, interceptor class # |-|-|-mergeConfig.js //Merge Configuration Items # |-|-|-settle.js //Processing Promise based on request status # |-|-|-createError.js//Generate the specified error # |-|-|-enhanceError.js//toJSON method specifying error object # |--|-transformData.js //Format responses and requests using transformRequest and transformResponse in default.js # |- 📁 helpers # |-|-|-bind.js//tool function # |--|-parseHeaders.js //Convert header information returned by getAllResponseHeaders into objects # |-|-|-buildURL.js //params parameter # |-|-|-cookies.js//Encapsulates methods to read, write, and delete cookies # |-|-|-isURLSameOrigin.js //Check if the current url is the same as the requested url # |--|-normalizeHeaderName.js //Format object property names, delete, create new case-sensitive properties # |-|-|-combineURLs.js //combined baseurl # |-|-|-isAbsoluteURL.js //Determine if it is an absolute path (refers to: //or//begins with an absolute path) # |- 📃 axios.js # |-Default configuration in defaults.js // axios # |-utils.js //Some tool methods # |-|-|-isFormData //Determine whether it is a formData object # | -| -isStandard BrowserEnv //Determine if the current environment is a standard browser environment # | -| -isUndefined //Determine whether it is undefined # |- |- ⏹ merge # |-|-|-isURLSearchParams //Determine whether it is a URLSearchParams object
Preface
This article focuses mainly on the source code of the main processes in axios, and skips the implementation of some tool functions.Please forgive me.If there are errors in the article, please point them out in time.
Overview of Request Process
Here is a rough code flow from the axios source when a request is made
Source Progressive Analysis
/lib/cancel/CancelToken.js
The code in CancelToken.js defines the behavior associated with canceling axios requests.However, the cancel method returned by CancelToken.source for canceling requests is based on the premise that CancelToken.source needs to be returned to token and combined into the config of the specific request to work properly.
How can I use the cancel request function in axios?
Before I looked at the Axios source code, I didn't even know the requests Axios could make, so let's first learn how to cancel a request in axios.Here is an example_
// axios class for cancelling requests const CancelToken = axios.CancelToken // The source method returns an object that contains // { // token, added to the config of the request to identify the request // Cancel, call cancel method to cancel request // } const source = CancelToken.source() axios.get('/info', { cancelToken: source.token }).catch(function(error) { if (axios.isCancel(error)) { console.log('Error Canceling Request') } else { // Other Errors } }) // Calling source.cancel cancels the request for axios.get('/info') source.cancel('Cancel Request')
Source code
var Cancel = require('./Cancel'); function CancelToken(executor) { if (typeof executor !== 'function') { throw new TypeError('executor must be a function.'); } var resolvePromise; // Create a Promise // The promise will remain pending until the cancel function is called this.promise = new Promise(function promiseExecutor(resolve) { resolvePromise = resolve; }); var token = this; executor(function cancel(message) { // Determine if the request has been cancelled if (token.reason) { return; } // Create cancel request information and add it to the reason property of the instance token.reason = new Cancel(message); // End pending state of this.promise // Set this.promise status to resolve resolvePromise(token.reason); }); } // Method to determine if the request has been cancelled CancelToken.prototype.throwIfRequested = function throwIfRequested() { if (this.reason) { throw this.reason; } }; CancelToken.source = function source() { var cancel; var token = new CancelToken(function executor(c) { cancel = c; }); return { token: token, cancel: cancel }; }; module.exports = CancelToken;
_Seeing this, we still can't understand how axios canceled a request.Because cancel returned by CancelToken.source alone cannot cancel a request, we need to understand it in conjunction with the code in xhr.js.
// /lib/adapters/xhr.js request.open() // ...omitted // If cancelToken option is configured if (config.cancelToken) { // Add a successful callback to Proise created in CancellToken // The callback is triggered when the CancelToken.source exposed cancel function is called config.cancelToken.promise.then(function onCanceled(cancel) { if (!request) { return; } // Cancel xhr request request.abort(); // Set promise returned by axios to reject state reject(cancel); request = null; }); } // ...omitted request.send()
You must have seen here that you have a general understanding of how requests are made in axios.To summarize, we created an additional PromiseA from CancelToken, mounted PromiseA into config, and exposed the resolve method of that PromiseA.We add monitoring of PromiseA's status before calling the send method (before sending the request), and when the PromiseA's status is modified, we cancel the request in the callback of PromiseA and set the PromiseB status returned by Axios to reject.In order to cancel the request
/lib/adapters/xhr.js
The xhrAdapter method exported by xhr.js is the default request method used by axios in a browser environment.We can use the adapter configuration item in the configuration to replace the default request method.
Source code
module.exports = function xhrAdapter(config) { return new Promise(function dispatchXhrRequest(resolve, reject) { var requestData = config.data; var requestHeaders = config.headers; // Determine if it is a FormData object, and if so, delete the Content-Type field of the header and let the browser set the Content-Type field automatically if (utils.isFormData(requestData)) { delete requestHeaders['Content-Type']; } // Create xtr object var request = new XMLHttpRequest(); // Set Authorization field in http request header // About Authorization fields // For more information, refer to https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Authorization if (config.auth) { var username = config.auth.username || ''; var password = config.auth.password || ''; // Encoding username and password using btoa method base64 requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password); } // Initialization Request Method // open(method: requested http method, url: requested URL address, whether asynchronous is supported) request.open( config.method.toUpperCase(), buildURL(config.url, config.params, config.paramsSerializer), true ); // Set timeout request.timeout = config.timeout; // Listens for changes in the readyState state, and when the readyState state is 4, the ajax request succeeds request.onreadystatechange = function handleLoad() { if (!request || request.readyState !== 4) { return; } // The digital status code of the request.status response, equal to 0 before the request is completed // A request.status error returns 0, but status equal to 0 is also a successful request except for the file Protocol // See https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/status for more information if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) { return; } // The getAllResponseHeaders method returns all response headers // For more information, see https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/getAllResponseHeaders var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null; // Get the request.response Text value if the data response type is not set (default is "json") or if the responseType is set to text, otherwise get the request response. // ResponsseType is an enumerated type, manually setting the type of returned data can be found at https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/responseType for more information // ResponsseText is a value that returns data from all backends as plain text. For more information, see https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/responseText // Response is text, and the type of response depends on the type of response. See more at https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/response var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response; var response = { data: responseData, // Response Content status: request.status, // Response Status statusText: request.statusText, // Text information for response status headers: responseHeaders, // Response Header config: config, request: request }; // status >= 200 && status < 300 resolve // Otherwise reject settle(resolve, reject, response); request = null; }; // Triggered when ajax is interrupted request.onabort = function handleAbort() { if (!request) { return; } // Throw Request aborted error reject(createError('Request aborted', config, 'ECONNABORTED', request)); request = null; }; // Triggered when ajax fails request.onerror = function handleError() { // Throw Network Error Error reject(createError('Network Error', config, null, request)); request = null; }; // ajax request timeout call request.ontimeout = function handleTimeout() { // Throw timeout error reject(createError('timeout of ' + config.timeout + 'ms exceeded', config, 'ECONNABORTED', request)); request = null; }; // Determine that the current browser environment is standard and, if so, add an xsrf header // What is xsrf header?Xsrf header is used to defend against CSRF attacks // The principle is that the service side generates an XSRF-TOKEN and saves it in the browser's cookie, and ajax sets the XSRF-TOKEN in the request header for each request // The server compares the XSRF-TOKEN in the cookie to the XSRF-TOKEN in the header // According to the homology policy, non-homologous sites cannot read and modify the site cookies of this source, avoiding forgery of cookies if (utils.isStandardBrowserEnv()) { var cookies = require('./../helpers/cookies'); // With Credentials Sets whether cookie s should be used in cross-domain requests See more at https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/withCredentials // (set withCredentials to true or homologous request) and set xsrfCookieName var xsrfValue = (config.withCredentials || isURLSameOrigin(config.url)) && config.xsrfCookieName ? // Read XSRF-TOKEN from cookie s cookies.read(config.xsrfCookieName) : undefined; if (xsrfValue) { // Setting XSRF-TOKEN in request header requestHeaders[config.xsrfHeaderName] = xsrfValue; } } // setRequestHeader is the method used to set the request header if ('setRequestHeader' in request) { // Set request headers configured in config, loop to request headers utils.forEach(requestHeaders, function setRequestHeader(val, key) { if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') { delete requestHeaders[key]; } else { request.setRequestHeader(key, val); } }); } // Set the withCredentials property of the xhr object to allow cookie s to request across domains if (config.withCredentials) { request.withCredentials = true; } // Set the responseType property of the xhr object if (config.responseType) { try { request.responseType = config.responseType; } catch (e) { if (config.responseType !== 'json') { throw e; } } } // Download Progress if (typeof config.onDownloadProgress === 'function') { request.addEventListener('progress', config.onDownloadProgress); } // Upload Progress // The request.upload XMLHttpRequest.upload attribute returns an XMLHttpRequestUpload object that represents the progress of the upload if (typeof config.onUploadProgress === 'function' && request.upload) { request.upload.addEventListener('progress', config.onUploadProgress); } if (config.cancelToken) { // Cancel requests, described in Introduction/lib/cancel/CancelToken.js and not covered here config.cancelToken.promise.then(function onCanceled(cancel) { if (!request) { return; } request.abort(); reject(cancel); request = null; }); } if (requestData === undefined) { requestData = null; } // Send http request request.send(requestData); }); };
/lib/core/dispatchRequest.js
The dispatchRequest.js file is where requests are actually called from the axios source.
Source code
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'); // Determines if the request has been cancelled and if canceled, the throw has been canceled function throwIfCancellationRequested(config) { if (config.cancelToken) { config.cancelToken.throwIfRequested(); } } module.exports = function dispatchRequest(config) { throwIfCancellationRequested(config); // Combine baseUrl with config.url if it contains baseUrl and is not an absolute path to config.url if (config.baseURL && !isAbsoluteURL(config.url)) { // Combining baseURL s with URLs to form a complete request path config.url = combineURLs(config.baseURL, config.url); } config.headers = config.headers || {}; // Format config.headers and config.data using the transformRequest method in/lib/defaults.js // For example, capitalize Accept, Content-Type in headers // For example, if the request body is an Object, it is formatted as a JSON string and application/json is added; charset=utf-8 Content-Type // And so on config.data = transformData( config.data, config.headers, config.transformRequest ); // Configuration priority of config.headers is higher when combining headers of different configurations config.headers = utils.merge( config.headers.common || {}, config.headers[config.method] || {}, config.headers || {} ); // Remove method attribute from headers utils.forEach( ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'], function cleanHeaderConfig(method) { delete config.headers[method]; } ); // If config configures adapter, use the alternative default request method in config that configures adapter var adapter = config.adapter || defaults.adapter; // Initiate requests using the adapter method (adapters vary depending on the browser or Node environment) return adapter(config).then( // Callback requesting correct return function onAdapterResolution(response) { // Determine if and cancel the request, and if canceled throws to cancel throwIfCancellationRequested(config); // Format the data returned by the server using the transformResponse method in/lib/defaults.js // For example, use JSON.parse to parse the response body response.data = transformData( response.data, response.headers, config.transformResponse ); return response; }, // Callbacks with failed requests 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); } ); };
/lib/core/InterceptorManager.js
The axios interceptor class is defined in the InterceptorManager.js file.Includes add, remove, loop interceptors.Both response and request interceptors are stored using arrays.
var utils = require('./../utils'); // Interceptor Class function InterceptorManager() { // handlers arrays are used to store interceptors this.handlers = []; } // Add interceptor, use method receives two parameters, successful callback and failed callback InterceptorManager.prototype.use = function use(fulfilled, rejected) { this.handlers.push({ // Successful callbacks fulfilled: fulfilled, // Failed callbacks rejected: rejected }); return this.handlers.length - 1; }; // Delete interceptors in instance handlers property based on ID (index) InterceptorManager.prototype.eject = function eject(id) { if (this.handlers[id]) { this.handlers[id] = null; } }; // Cycle Interceptor InterceptorManager.prototype.forEach = function forEach(fn) { utils.forEach(this.handlers, function forEachHandler(h) { if (h !== null) { fn(h); } }); }; module.exports = InterceptorManager;
/lib/core/Axios.js
The Axios.js file defines the request, get, post, delete methods on the Axios instance.Get, post, delete and other methods are based on Axios.prototype.request encapsulation.In Axios.prototype.request, request interceptors, dispatchRequest (actually initiated), are executed in turn to respond to interceptors.The overall process is shown in the diagram above.
Source code
var utils = require('./../utils'); var buildURL = require('../helpers/buildURL'); var InterceptorManager = require('./InterceptorManager'); var dispatchRequest = require('./dispatchRequest'); var mergeConfig = require('./mergeConfig'); function Axios(instanceConfig) { // Configuration of Axios this.defaults = instanceConfig; // Interceptor this.interceptors = { request: new InterceptorManager(), // request interceptor response: new InterceptorManager() // Response Interceptor }; } Axios.prototype.request = function request(config) { // If config is a string, treat the string as the url address of the request if (typeof config === 'string') { config = arguments[1] || {}; config.url = arguments[0]; } else { config = config || {}; } // Consolidated Configuration config = mergeConfig(this.defaults, config); // If no request method is specified, use the get method config.method = config.method ? config.method.toLowerCase() : 'get'; var promise = Promise.resolve(config); // Combine request interceptors, response interceptors, and actual request (dispatchRequest) methods into an array similar to the following structure // [Request Interceptor 1 success, Request Interceptor 1 error, Request Interceptor 2 success, Request Interceptor 2 error, dispatchRequest, undefined, Response Interceptor 1 success, Response Interceptor 1 error] var chain = [dispatchRequest, undefined]; this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { chain.unshift(interceptor.fulfilled, interceptor.rejected); }); this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { chain.push(interceptor.fulfilled, interceptor.rejected); }); // Start the entire request process (request interceptor - > dispatchRequest - > response interceptor) // The process can be interpreted as the picture above while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); } return promise; }; Axios.prototype.getUri = function getUri(config) { config = mergeConfig(this.defaults, config); return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, ''); }; // Encapsulate other methods based on Axios.prototype.request // Add delete, get, head, options, post, put, patch to the prototype chain of Axios.prototype // Axios.prototype.delete = // Axios.prototype.get = // Axios.prototype.head = // Axios.prototype.options = // ... 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 })); }; }); 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 })); }; }); module.exports = Axios;
/lib/defaults.js
The defaults.js file configures the default request header for axios, the default request method for Axios in different environments, the method for formatting the request body, the method for formatting the response body, and so on.
Source code
var utils = require('./utils'); var normalizeHeaderName = require('./helpers/normalizeHeaderName'); // Default Content-Type var DEFAULT_CONTENT_TYPE = { 'Content-Type': 'application/x-www-form-urlencoded' }; // Set ContentType, without setting function setContentTypeIfUnset(headers, value) { if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) { headers['Content-Type'] = value; } } // Get the default request method based on the current environment function getDefaultAdapter() { var adapter; // Determine if a process object exists in the current environment if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') { // node environment adapter = require('./adapters/http'); } else if (typeof XMLHttpRequest !== 'undefined') { // Browser Environment adapter = require('./adapters/xhr'); } return adapter; } var defaults = { // Default Request Method adapter: getDefaultAdapter(), // Format request data, which will be used before the request is sent transformRequest: [ function transformRequest(data, headers) { // Format the header property name to format the nonstandard property name in the header to the Accept property name normalizeHeaderName(headers, 'Accept'); // Format the header attribute name, format the nonstandard attribute name in the header as the Content-Type attribute name normalizeHeaderName(headers, 'Content-Type'); if (utils.isFormData(data) || utils.isArrayBuffer(data) || utils.isBuffer(data) || utils.isStream(data) || utils.isFile(data) || utils.isBlob(data) ) { return data; } if (utils.isArrayBufferView(data)) { return data.buffer; } // URLSearchParams provides some string interfaces for handling URL queries // If it is a URLSearchParams object if (utils.isURLSearchParams(data)) { // Content-Type is set to application/x-www-form-urlencoded // application/x-www-form-urlencoded, data is encoded as key-value pairs separated by setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8'); return data.toString(); } // If it is an object if (utils.isObject(data)) { // Content-Type set to application/json setContentTypeIfUnset(headers, 'application/json;charset=utf-8'); // Format the request body as a JSON string and return return JSON.stringify(data); } return data; } ], // Format the response resposeData, which will be used when the response is accepted transformResponse: [ function transformResponse(data) { if (typeof data === 'string') { try { data = JSON.parse(data); } catch (e) { /* Ignore */ } } return data; } ], // Default timeout timeout: 0, // The key of the cookie set by xsrf xsrfCookieName: 'XSRF-TOKEN', // xsrf sets the header's key xsrfHeaderName: 'X-XSRF-TOKEN', maxContentLength: -1, // Verify the status of the request // Romise is used when processing requests validateStatus: function validateStatus(status) { return status >= 200 && status < 300; } }; defaults.headers = { // Common HTTP Fields // Accept tells clients what types they can handle common: { 'Accept': 'application/json, text/plain, */*' } }; utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) { defaults.headers[method] = {}; }); // Set default Content-Type for post, put, patch request utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE); }); module.exports = defaults;
/lib/axios.js
The axios.js file is the entry method to the Axios tool library, at axios.js
var utils = require('./utils'); var bind = require('./helpers/bind'); var Axios = require('./core/Axios'); var mergeConfig = require('./core/mergeConfig'); var defaults = require('./defaults'); // Create axios instance function createInstance(defaultConfig) { var context = new Axios(defaultConfig); // Change this of Axios.prototype.request to execute the context instance // instance equals the Axios.prototype.request method var instance = bind(Axios.prototype.request, context); // Merge attributes on Axios.prototype, context into instance // instance.get = Axios.prototype.get // instance.defaults = context.defaults // ... utils.extend(instance, Axios.prototype, context); utils.extend(instance, context); return instance; } // Axios exposes a axios.request method directly to the user, so we can use it when using axios.You do not need an instance of new Axios // import axios from 'axios' // axios.get('/info') var axios = createInstance(defaults); axios.Axios = Axios; // axios.create can generate a new axios instance based on a user-defined config axios.create = function create(instanceConfig) { return createInstance(mergeConfig(axios.defaults, instanceConfig)); }; axios.Cancel = require('./cancel/Cancel'); axios.CancelToken = require('./cancel/CancelToken'); axios.isCancel = require('./cancel/isCancel'); axios.all = function all(promises) { return Promise.all(promises); }; axios.spread = require('./helpers/spread'); module.exports = axios; module.exports.default = axios;