一文深入浅出解析Axios源码

2023-08-23 0 591

一文深入浅出解析Axios源码

译者:告士(时评译者)

转贴镜像:

https://mp.weixin.qq.com/s/QzTD0bGMC77x66kALYmQbA

责任编辑概述

序言Axios总体标识符内部结构预测Axios继续执行业务流程总体预测Axios继续执行文档预测

序言

axios 是现阶段最常见的 http 允诺库,能用作应用程序和 node.js , 在 github 上已近 43k star 以内之多。

Axios 的主要就优点主要就包括:

如前所述 Promise全力支持应用程序和 node.js可加进圣夫龙和切换允诺和积极响应统计数据允诺能中止手动切换 JSON 统计数据应用程序全力支持严防 XSRF

责任编辑 将 带我们一同写作 axios 的源标识符, 依照继续执行业务流程导出之中的许多PCB基本功,预测机能同时实现。

Axios总体标识符内部结构预测

├── /dist/ # 工程项目输入产品目录 ├── /lib/ # 工程项目源标识符产品目录 │ ├── /cancel/ # 表述中止机能 │ ├── /core/ # 许多核心理念机能│ │ ├── Axios.js# axios的核心理念主类 │ │ ├── dispatchRequest.js # 用以初始化http允诺转接器方式推送允诺│ │ ├── InterceptorManager.js# 圣夫龙缺省 │ │ └── settle.js # 依照http积极响应状况,发生改变Promise的状况 │ ├── /helpers/ # 许多远距方式 │ ├── /adapters/ # 表述允诺的转接器 xhr、http │ │ ├── http.js # 同时实现http转接器 │ │ └── xhr.js # 同时实现xhr转接器 │ ├── axios.js # 对外暴露接口 │ ├── defaults.js # 默认配置 │ └── utils.js # 公用工具 ├── package.json # 工程项目信息 ├── index.d.ts # 配置TypeScript的声明文档 └── index.js # 入口文档

Axios继续执行业务流程总体预测

一文深入浅出解析Axios源码

Axios继续执行文档预测

入口 /lib/axios.jsfunction createInstance(defaultConfig) { var context = newAxios(defaultConfig);// 自表述bind方式,等同于var instance = Axios.prototype.request.bind(context) // 这句的作用是将request方式指向instance,上下文是context,能使用instance(option) // 初始化 var instance = bind(Axios.prototype.request, context); // 依照前面extend方式,使Axios.prototype的方式扩展到instance对象上,这样instance就有了get,post,put等方式 // 并且制定上细纹是context, 这样axios中方式, this继续执行context // utils.extend(instance, Axios.prototype, context); // 将context自身属性和方式扩展到instance上, 因为extend内部使用的forEach方式对对象做for in 遍历时, 只遍历对象本身的属性, 而不会遍历原型链上的属性 // 这样,instance 就有了 defaults、interceptors 属性。(这两个属性后面我们会介绍) // Copy context to instance utils.extend(instance, context); returninstance; }// 接收默认配置项作为参数(后面会介绍配置项),创建一个Axios实例,最终会被作为对象导出 var axios = createInstance(defaults);

/lib/core/Axios.js是核心理念包,axios各种方式(delete, get, head, options,post, put, patch)都是通过request方式发出的。每个axios实例都有一个interceptors实例属性,interceptors对象上有两个属性request、response。InterceptorManager用以同时实现圣夫龙的,这个缺省原型上有3个方式:use、eject、forEach。我们首先看下 InterceptorManager的同时实现:

圣夫龙包 /lib/core/InterceptorManager.jsfunction InterceptorManager() { this.handlers = []; // 存放圣夫龙方式,数组内每一项都是有两个属性的对象,两个属性分别对应成功和失败后继续执行的函数。 } // 往圣夫龙里加进拦截方式,使用 unshift,会导致use 后加进的先继续执行,先加进的后继续执行,use就是我们在http.js中书写的圣夫龙 InterceptorManager.prototype.use = function use(fulfilled, rejected) { this.handlers.push({ fulfilled: fulfilled, rejected: rejected }); return this.handlers.length –1; }; // 用以注销指定的圣夫龙 InterceptorManager.prototype.eject = function eject(id) { if (this.handlers[id]) {this.handlers[id] = null; } }; // 遍历this.handlers,并将this.handlers里的每一项作为参数传给fn继续执行InterceptorManager.prototype.forEach =function forEach(fn) { utils.forEach(this.handlers, function forEachHandler(h) { if (h !== null) { fn(h); } }); };核心理念包 /lib/core/Axios.js

Axios.js主要就处理了两个部分,复用允诺重载。 圣夫龙是axios的一大特色,它的同时实现原理其实不复杂,核心理念就是promise的链式初始化。

一文深入浅出解析Axios源码
function Axios(instanceConfig) { this.defaults = instanceConfig;this.interceptors = { request: new InterceptorManager(), response: newInterceptorManager() }; } Axios.prototype.request =function request(config) { /*eslint no-param-reassign:0*/ // Allow for axios(example/url[, config]) a la fetch API if (typeof config === string) { config = arguments[1] || {}; config.url =arguments[0]; } else{ config = config || {}; } config = mergeConfig(this.defaults, config); config.method = config.method ? config.method.toLowerCase() : get; // Hook up interceptors middleware // chain 是一个数组 var chain = [dispatchRequest, undefined]; varpromise =Promise.resolve(config); // 使用 use 加进 fulfilled 与 rejected 加进到队列中 // 加进 request 拦截函数的时候使用的是unshift, 这样会导致 use 后加进的先继续执行,先加进的后继续执行 this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { chain.unshift(interceptor.fulfilled, interceptor.rejected); }); // response使用的是push,加进拦截相应函数,这里是先加进先继续执行,后加进后继续执行。 this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { chain.push(interceptor.fulfilled, interceptor.rejected); }); // chain [fulfilled, rejected, … dispatchRequest, undefined ….,fulfilled, rejected] // 这里补充一下 fulfilled, rejected 都是肯定是成对出现的, 具体原因可看 InterceptorManager.prototype.use // promise.then(undefined, undefined) 中当传递的不是function时,会发生值穿。也就是说 use 中能传入非function, // 或者传入单个function while(chain.length) { promise = promise.then(chain.shift(), chain.shift()); }return promise; }; // 复用request 同时实现了 delete, get, head, optionsutils.forEach([delete, get, head, options], function forEachMethodNoData(method) { /*eslint func-names:0*/Axios.prototype[method] =function(url, config) { return this.request(utils.merge(config || {}, { method: method, url: url })); }; }); // 复用request 同时实现了 delete, get, head, options utils.forEach([post, put, patch], function forEachMethodWithData(method) { /*eslint func-names:0*/ Axios.prototype[method] = function(url, data, config) { return this.request(utils.merge(config || {}, { method: method, url: url, data: data })); }; });

Axios.prototype.request方式会初始化dispatchRequest方式,而dispatchRequest方式会初始化xhrAdapter方式,xhrAdapter方式返回的是还一个Promise对象,初始化defaults的getDefaultAdapter方式,如果是应用程序端使用xhr,如果是服务端初始化的是http。接下来我们一次导出dispatchRequest,xhrAdapter,xhr 文档。

/lib/core/dispatchRequest.js

dispatchRequest做了三件事

拿到config对象,对config进行传给http允诺转接器前的最后处理http允诺转接器依照config配置,发起允诺http允诺转接器允诺完成后,如果成功则依照header、data、和config.transformResponsemodule.exports =function dispatchRequest(config) { throwIfCancellationRequested(config); // 如果包含baseUrl, 并且不是config.url绝对路径,组合baseUrl以及config.url if (config.baseURL && !isAbsoluteURL(config.url)) { // 组合baseURL与url形成完整的允诺路径config.url = combineURLs(config.baseURL, config.url); } config.headers =config.headers || {}; // 使用/lib/defaults.js中的transformRequest方式,对config.headers和config.data进行格式化 // 比如将headers中的Accept,Content-Type统一处理成大写 // 比如如果允诺正文是一个Object会格式化为JSON字符串,并加进application/json;charset=utf-8的Content-Type // 等一系列操作 config.data = transformData( config.data, config.headers,config.transformRequest ); // 合并不同配置的headers,config.headers的配置优先级更高 config.headers = utils.merge(config.headers.common || {}, config.headers[config.method] || {}, config.headers || {} ); // 删除headers中的method属性 utils.forEach( [delete, get, head, post, put, patch, common], function cleanHeaderConfig(method) { delete config.headers[method]; } ); // 如果config配置了adapter,使用config中配置adapter的替代默认的允诺方式 var adapter = config.adapter || defaults.adapter; // 使用adapter方式发起允诺(adapter依照应用程序环境或者Node环境会有不同,http允诺转接器会优先使用config上自表述的转接器,没有配置时才会使用默认的XHR或http转接器,不过大部分时候,axios提供的默认转接器是能够满足我们的return adapter(config).then( // 允诺正确返回的回调 function onAdapterResolution(response) { // 判断是否以及中止了允诺,如果中止了允诺抛出以中止 throwIfCancellationRequested(config); // 使用/lib/defaults.js中的transformResponse方式,对服务器返回的统计数据进行格式化 // 例如,使用JSON.parse对积极响应正文进行导出 response.data = transformData( response.data, response.headers,config.transformResponse ); returnresponse; }, // 允诺失败的回调function onAdapterRejection(reason) { if(!isCancel(reason)) { throwIfCancellationRequested(config); if(reason && reason.response) { reason.response.data = transformData( reason.response.data, reason.response.headers,config.transformResponse ); } } returnPromise.reject(reason); } ); };

dispatchRequest方式内,首先得到xhrAdapter方式返回的Promise对象, 然后通过.then方式,对xhrAdapter返回的Promise对象的成功或失败结果再次加工, 成功的话,则将处理后的response返回, 失败的话,则返回一个状况为rejected的Promise对象. 用户初始化axios()方式时,就能直接初始化Promise的.then或.catch进行业务处理了。

return adapter(config).then(function onAdapterResolution(response) { // … return response; }, function onAdapterRejection(reason) { // … return Promise.reject(reason); }); };

能看到adapter 若config没有配置adapter使用的是defaults的adapter,我们首先看下defaults中的adapter函数,稍后会详细介绍defaults源标识符,

function getDefaultAdapter() { var adapter; // Only Node.JS has a process variable that is of [[Class]] process if (typeof process !== undefined && Object.prototype.toString.call(process) === [object process]) { // For node use HTTP adapter adapter = require(./adapters/http); } else if (typeofXMLHttpRequest !==undefined) { // For browsers use XHR adapter adapter = require(./adapters/xhr); } returnadapter; }

初始化defaults的getDefaultAdapter方式,如果是应用程序端使用xhr,如果是服务端初始化的是http。我们接下来看下xhr文档。

/lib/adapters/xhr.js

xhr.js导出的xhrAdapter方式是axios在应用程序环境下使用的默认允诺方式。我们能在配置中使用adapter配置项对默认允诺方式进行替换。

module.exports = function xhrAdapter(config) { return new Promise(function dispatchXhrRequest(resolve, reject) { varrequestData = config.data;var requestHeaders = config.headers; // 判断是否是FormData对象, 如果是, 删除header的Content-Type字段,让浏览器手动设置Content-Type字段 if (utils.isFormData(requestData)) { delete requestHeaders[Content-Type]; } // 创建xtr对象 var request = new XMLHttpRequest(); // 设置http允诺头中的Authorization字段 // 关于Authorization字段 // 更多内容参考https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Authorization if (config.auth) { varusername = config.auth.username || ;var password = config.auth.password || ; // 使用btoa方式base64编码username和passwordrequestHeaders.Authorization =Basic + btoa(username + : + password); } // 初始化允诺方式 // open(method: 允诺的http方式, url: 允诺的url地址, 是否全力支持异步)request.open( config.method.toUpperCase(), buildURL(config.url, config.params, config.paramsSerializer),true ); // 设置超时时间 request.timeout = config.timeout; // 监听readyState状况的变化,当readyState状况为4的时候,表示ajax允诺成功 request.onreadystatechange = function handleLoad() { if(!request || request.readyState !==4) { return; } // request.status积极响应的数字状况码,在完成允诺前数字状况码等于0 // 如果request.status出错返回的也是0,但是file协议除外,status等于0也是一个成功的允诺 // 更多内容请参考 https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/status if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf(file:) === 0)) { return; } // getAllResponseHeaders方式会返回所有的积极响应头 // 更多内容请参考 https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/getAllResponseHeaders var responseHeaders = getAllResponseHeaders inrequest ? parseHeaders(request.getAllResponseHeaders()) :null; // 如果没有设置统计数据积极响应类型(默认为“json”)或者responseType // responseType是一个枚举类型,手动设置返回统计数据的类型 更多请参考 https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/responseType // responseText是全部后端的返回统计数据为纯文本的值 更多请参考 https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/responseText // response为正文,response的类型取决于responseType 更多请参考 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, // 积极响应正文 status: request.status, // 积极响应状况 statusText: request.statusText,// 积极响应状况的文本信息 headers: responseHeaders, // 积极响应头 config: config, request: request }; // status >= 200 && status < 300 resolve // 否则reject settle(resolve, reject, response); request = null; }; // ajax中断时触发 request.onabort = function handleAbort() { if (!request) { return; } // 抛出Request aborted错误 reject(createError(Request aborted, config, ECONNABORTED, request)); request = null; };// ajax失败时触发 request.onerror = function handleError() { // 抛出Network Error错误 reject(createError(Network Error, config, null, request)); request = null; }; // ajax允诺超时时初始化 request.ontimeout = function handleTimeout() { // 抛出 timeout错误 reject(createError(timeout of + config.timeout + ms exceeded, config, ECONNABORTED, request)); request = null; }; // 判断当前是为标准应用程序环境,如果是,加进xsrf头 // 什么是xsrf header?xsrf header是用以防御CSRF攻击 // 原理是服务端生成一个XSRF-TOKEN,并保存到应用程序的cookie中,在每次允诺中ajax都会将XSRF-TOKEN设置到request header中 // 服务器会比较cookie中的XSRF-TOKEN与header中XSRF-TOKEN是否一致 // 依照同源策略,非同源的网站无法读取修改本源的网站cookie,避免了伪造cookie if(utils.isStandardBrowserEnv()) {var cookies = require(./../helpers/cookies); // withCredentials设置跨域允诺中是否应该使用cookie 更多请参考 https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/withCredentials // (设置了withCredentials为true或者是同源允诺)并且设置xsrfCookieName varxsrfValue = (config.withCredentials || isURLSameOrigin(config.url)) && config.xsrfCookieName ?// 读取cookie中XSRF-TOKENcookies.read(config.xsrfCookieName) :undefined; if (xsrfValue) { // 在request header中设置XSRF-TOKENrequestHeaders[config.xsrfHeaderName] = xsrfValue; } }// setRequestHeader是用以设置允诺头部的方式 if (setRequestHeader in request) { // 将config中配置的requestHeaders,循环设置到允诺头上 utils.forEach(requestHeaders, function setRequestHeader(val, key) { if (typeofrequestData ===undefined && key.toLowerCase() === content-type) { delete requestHeaders[key]; } else{ request.setRequestHeader(key, val); } }); }// 设置xhr对象的withCredentials属性,是否允许cookie进行跨域允诺 if (config.withCredentials) { request.withCredentials = true; } // 设置xhr对象的responseType属性 if(config.responseType) {try { request.responseType = config.responseType; } catch (e) { if(config.responseType !==json) { throw e; } } } // 下载进度 if (typeof config.onDownloadProgress === function) { request.addEventListener(progress, config.onDownloadProgress); } // 上传进度 // request.upload XMLHttpRequest.upload 属性返回一个 XMLHttpRequestUpload对象,用以表示上传的进度 if (typeof config.onUploadProgress === function && request.upload) { request.upload.addEventListener(progress, config.onUploadProgress); }if (config.cancelToken) { // 中止允诺,在介绍/lib/cancel/CancelToken.js中以及介绍,这里不在赘述config.cancelToken.promise.then(function onCanceled(cancel) { if (!request) { return; } request.abort(); reject(cancel); request =null; }); } if (requestData === undefined) { requestData = null; } // 推送http允诺 request.send(requestData); }); };

xhrAdapter内的XHR推送允诺成功后会继续执行这个Promise对象的resolve方式,并将允诺的统计数据传出去, 反之则继续执行reject方式,并将错误信息作为参数传出去。接下来走settle.js.

function settle(resolve, reject, response){ var validateStatus = response.config.validateStatus; if (!response.status|| !validateStatus || validateStatus(response.status)) { resolve(response); } else { reject( /**/ ); } };

dispatch 中 transformData对统计数据进行处理的,以及defaults的getDefaultAdapter方式,我们接下来看下/lib/defaults.js。

/lib/defaults.js

defaults.js文档中配置了,axios默认的允诺头、不同的环境下axios默认使用的允诺方式、格式化允诺正文的方式,格式化积极响应正文方式等内容

var utils = require(./utils); varnormalizeHeaderName =require(./helpers/normalizeHeaderName); // 默认Content-Type varDEFAULT_CONTENT_TYPE = {Content-Type: application/x-www-form-urlencoded }; // 设置ContentType,在没有设置的情况下 function setContentTypeIfUnset(headers, value) { if (!utils.isUndefined(headers) && utils.isUndefined(headers[Content-Type])) { headers[Content-Type] = value; } } function getDefaultAdapter() { varadapter;// 判断当前环境是否存在process对象 if (typeof process !== undefined && Object.prototype.toString.call(process) === [object process]) { // node环境 adapter = require(./adapters/http); } else if (typeof XMLHttpRequest !== undefined) { // 应用程序环境 adapter = require(./adapters/xhr); } return adapter; } var defaults = { // 默认的允诺方法 adapter: getDefaultAdapter(), // 格式化允诺requestData,这会允诺推送前使用,在默认情况下,axios将会手动的将传入的data对象序列化为JSON字符串, transformRequest: [ function transformRequest(data, headers) { // 格式化header属性名,将header中不标准的属性名,格式化为Accept属性名 normalizeHeaderName(headers, Accept); // 格式化header属性名,将header中不标准的属性名,格式化为Content-Type属性名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提供了许多用以处理URL查询字符串接口 // 如果是URLSearchParams对象 if (utils.isURLSearchParams(data)) { // Content-Type设置为application/x-www-form-urlencoded // application/x-www-form-urlencoded,统计数据被编码成以&分隔的键值对setContentTypeIfUnset(headers,application/x-www-form-urlencoded;charset=utf-8); return data.toString(); } // 如果是对象 if(utils.isObject(data)) {// Content-Type设置为application/json setContentTypeIfUnset(headers, application/json;charset=utf-8); // 将允诺正文格式化为JSON字符串,并返回 return JSON.stringify(data); } return data; } ], // 格式化积极响应resposeData,这会积极响应接受后使用,将积极响应统计数据中的JSON字符串切换为JavaScript对象 transformResponse: [ function transformResponse(data) { if (typeof data === string) { try { data = JSON.parse(data); } catch (e) { /* Ignore */} }return data; } ], // 默认超时时间 timeout: 0, // xsrf设置的cookie的key xsrfCookieName: XSRF-TOKEN, // xsrf设置header的key xsrfHeaderName: X-XSRF-TOKEN, maxContentLength: -1, // 验证允诺的状况 // 在处理允诺的Promise会被使用 validateStatus: function validateStatus(status) { return status >= 200 && status < 300; } }; defaults.headers = {// 通用的HTTP字段 // Accept告知应用程序能处理的类型 common: { Accept: application/json, text/plain, */*} }; utils.forEach([delete, get, head], function forEachMethodNoData(method) { defaults.headers[method] = {}; });// 为post,put,patch允诺设置默认的Content-Type utils.forEach([post, put, patch], function forEachMethodWithData(method) { defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE); }); module.exports = defaults;

默认的defaults配置项里已经自表述了一个允诺切换器和一个积极响应切换器,允诺切换器的使用地方是http允诺前,使用允诺切换器对允诺统计数据做处理, 然后传给http允诺转接器使用,在默认情况下,axios将会手动的将传入的data对象序列化为JSON字符串,将积极响应统计数据中的JSON字符串切换为JavaScript对象,看下transformData方式的标识符, 主要就遍历切换器数组,分别继续执行每一个切换器,依照data和headers参数,返回新的data。积极响应切换器的使用地方是在http允诺完成后,依照http允诺转接器的返回值做统计数据切换处理。

/lib/core/transformData.jsfunction transformData(data, headers, fns) { utils.forEach(fns, function transform(fn) { data = fn(data, headers); }); return data; }; // /lib/core/dispatchRequest.js returnadapter(config).then(function onAdapterResolution(response) { // …response.data = transformData( response.data, response.headers, config.transformResponse );return response; }, function onAdapterRejection(reason) { if (!isCancel(reason)) { // … if(reason && reason.response) { reason.response.data = transformData( reason.response.data, reason.response.headers, config.transformResponse ); } }return Promise.reject(reason); });

圣夫龙同样能同时实现切换允诺和积极响应统计数据的需求,但依照译者的设计和综合代码能看出, 在允诺时,圣夫龙主要就负责修改config配置项,统计数据切换器主要就负责切换允诺体,比如切换对象为字符串 在允诺积极响应后,圣夫龙能拿到response,统计数据切换器主要就负责处理积极响应体,比如切换字符串为对象。

axios 的大体业务流程 如上述般大体介绍完了。

译者:告士(时评译者)

转贴镜像:

https://mp.weixin.qq.com/s/QzTD0bGMC77x66kALYmQbA

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务