早期axios0.1.0版本做了对IE浏览器与包含XmlHttpRequest的浏览器的支持。并且做了对请求参数拼接、Json对象序列化等基本功能。

到0.19.0版本时,内部请求已经变为了在Node环境下与主流浏览器的支持,其中Node环境下支持http请求与https请求。并且支持取消、拦截。

Axios执行开始之初,首先执行createInstance方法,createInstance方法用来创建一个新的Axios实例。但这里奇怪的是,返回的示例并不是真正new出来的实例,而是一个幻影。实际在执行时,内部代码的指向还是内部的Axios对象。Axios内部使用了wrap来表示各个方法,可能真的是为了将真实的Axios实例隐藏。这么做作用在于防止外部修改内部的方法,做好了封装和防护。

function createInstance(defaultConfig) {var context = new Axios(defaultConfig);var instance = bind(Axios.prototype.request, context);// Copy axios.prototype to instanceutils.extend(instance, Axios.prototype, context);// Copy context to instanceutils.extend(instance, context);return instance;
}

不过,外部代码还是可以访问到内部的Axios实例的,在创建了幻影之后,继续执行以下代码:

// Expose Axios class to allow class inheritance
axios.Axios = Axios;// Factory for creating new instances
axios.create = function create(instanceConfig) {return createInstance(mergeConfig(axios.defaults, instanceConfig));
};// Expose Cancel & CancelToken
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');// Expose all/spread
axios.all = function all(promises) {return Promise.all(promises);
};axios.spread = require('./helpers/spread');module.exports = axios;// Allow use of default import syntax in TypeScript
module.exports.default = axios;

好,这里是外部的一些构造。其实我们拿到axios对象时就可以发起请求了。接下来我们通过一个使用示例来说明请求过程。以下是我们的request请求层示例:

// 使用示例,业务网络层request.js
const service = axios.create({baseURL: BASE_API, timeout: TIMEOUT
});service.interceptors.request.use(config => {return config;},error => {Promise.reject(error);}
);service.interceptors.response.use(response => {return Promise.reject(response);},error => {return Promise.reject(error);}
);export default service;

假设我们的业务网络层是上面的用法,然后在具体的业务代码处通过get方法发起了一次业务请求:

axios.get('/get/server').then(function (response) {}).catch(function (err) {});

当业务请求代码发起时,具体执行的是lib/core/Axios.js中的request方法:

// lib/core/Axios.js
Axios.prototype.request = function request(config) {/*eslint no-param-reassign:0*/// Allow for axios('example/url'[, config]) a la fetch APIif (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 middlewarevar chain = [dispatchRequest, undefined];var promise = Promise.resolve(config);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);});while (chain.length) {promise = promise.then(chain.shift(), chain.shift());}return promise;
};

request方法的参数为一个config对象,而这个对象是由以下信息组成的:

{method: 'get',url: '/get/server'
}

紧接着会通过mergeConfig方法将自定义的config对象与默认的config对象进行合并:

// lib/core/Axios.jsconfig = mergeConfig(this.defaults, config);

而这里的this.defaults实际内部如下:

// lib/defaults.js
var defaults = {adapter: getDefaultAdapter(),// 默认的请求转换transformRequest: [function transformRequest(data, headers) {normalizeHeaderName(headers, 'Accept');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;}if (utils.isURLSearchParams(data)) {setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');return data.toString();}if (utils.isObject(data)) {setContentTypeIfUnset(headers, 'application/json;charset=utf-8');return JSON.stringify(data);}return data;}],// 默认的相应转换,自定义转换方法,Json解析transformResponse: [function transformResponse(data) {/*eslint no-param-reassign:0*/if (typeof data === 'string') {try {data = JSON.parse(data);} catch (e) { /* Ignore */ }}return data;}],/*** A timeout in milliseconds to abort a request. If set to 0 (default) a* timeout is not created.*/timeout: 0,xsrfCookieName: 'XSRF-TOKEN',xsrfHeaderName: 'X-XSRF-TOKEN',maxContentLength: -1,validateStatus: function validateStatus(status) {return status >= 200 && status < 300;}
};

总之是一些待会会用到的默认配置。合并的机制是优先取自定义的配置,再取默认配置,组成一个新的合并对象。

request的方法继续向下,到了关键的地方:

// lib/core/Axios.js// Hook up interceptors middlewarevar chain = [dispatchRequest, undefined];var promise = Promise.resolve(config);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);});

由于咱们的业务网络层使用了interceptors.request.useinterceptors.response.use这样的字样,所以我们应该看看对应的use方法做了什么:

// lib/core/InterceptorManager.js
InterceptorManager.prototype.use = function use(fulfilled, rejected) {this.handlers.push({fulfilled: fulfilled,rejected: rejected});return this.handlers.length - 1;
};

而InterceptorManager的构造方法如下:

// lib/core/InterceptorManager.js
function InterceptorManager() {this.handlers = [];
}

InterceptorManager在构造之初内部只有一个数组属性handlers,所以当业务网络层使用use方法时,会将对应的fulfilled, rejected组成一个新的对象Push到这个数组中。所以再回来forEach的地方:

// lib/core/InterceptorManager.js
InterceptorManager.prototype.forEach = function forEach(fn) {utils.forEach(this.handlers, function forEachHandler(h) {if (h !== null) {fn(h);}});
};

forEach内部仅仅将handlers遍历,将数组中的对象通过forEach的回调方法传出,所以在Axios的request方法内,就是将业务网络层所定义的request interceptors与response interceptors压入数组chain中。所以在两个forEach执行完之后,数组chain的执行如下:

chain = [request.interceptors.success, request.interceptors.fail, dispatchRequest, undefined, response.interceptors.success, response.interceptors.fail]

然后就是根本性的一环:

// lib/core/Axios.jswhile (chain.length) {promise = promise.then(chain.shift(), chain.shift());}

当while方法执行时,开始触发chain中所塞进去的各个方法。在这里首先执行的是request.interceptors.success,也就是业务网络层所定义的请求拦截层。在我们的示例中什么处理都没做,于是再执行dispatchRequest。这个dispatchRequest可大有来头,是请求的核心:

// lib/core/dispatchRequest.js
function dispatchRequest(config) {throwIfCancellationRequested(config);// Support baseURL configif (config.baseURL && !isAbsoluteURL(config.url)) {config.url = combineURLs(config.baseURL, config.url);}// Ensure headers existconfig.headers = config.headers || {};// Transform request dataconfig.data = transformData(config.data,config.headers,config.transformRequest);// Flatten headersconfig.headers = utils.merge(config.headers.common || {},config.headers[config.method] || {},config.headers || {});// 为什么要删除Header中的请求方式?utils.forEach(['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],function cleanHeaderConfig(method) {delete config.headers[method];});var adapter = config.adapter || defaults.adapter;return adapter(config).then(function onAdapterResolution(response) {throwIfCancellationRequested(config);// Transform response data// 允许自定义转换方法response.data = transformData(response.data,response.headers,config.transformResponse);return response;}, function onAdapterRejection(reason) {if (!isCancel(reason)) {throwIfCancellationRequested(config);// Transform response dataif (reason && reason.response) {reason.response.data = transformData(reason.response.data,reason.response.headers,config.transformResponse);}}return Promise.reject(reason);});
};

dispatchRequest方法执行了以下事情:

  • 1.校验这次的请求是否已取消。
  • 2.构造完整的请求Url。
  • 3.确认header存在。
  • 4.转换请求数据与header。转换方法在这里也可以通过config自定义。默认转换为Json串。
  • 5.合并config.headers。
  • 6.删除config.headers中请求方法所对应的值。
  • 7.获取对应的网络请求适配器并发起请求。默认的adapter主要有两种:1.浏览器环境下为XMLHttpRequest. 2.Node环境下为https库或者http库,这里还支持自定义网路请求框架。

在这里代码分析我们假设运行环境为浏览器,那我们进入浏览器环境的dispatchRequest方法:

// lib/adapters/xhr.js
function xhrAdapter(config) {return new Promise(function dispatchXhrRequest(resolve, reject) {var requestData = config.data;var requestHeaders = config.headers;if (utils.isFormData(requestData)) {delete requestHeaders['Content-Type']; // Let the browser set it}var request = new XMLHttpRequest();// HTTP basic authenticationif (config.auth) {var username = config.auth.username || '';var password = config.auth.password || '';requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);}// 这里的method从哪来的?request.open(config.method.toUpperCase(), buildURL(config.url, config.params, config.paramsSerializer), true);// Set the request timeout in MSrequest.timeout = config.timeout;// Listen for ready staterequest.onreadystatechange = function handleLoad() {if (!request || request.readyState !== 4) {return;}// The request errored out and we didn't get a response, this will be// handled by onerror instead// With one exception: request that using file: protocol, most browsers// will return status as 0 even though it's a successful requestif (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {return;}// Prepare the responsevar responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;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};settle(resolve, reject, response);// Clean up requestrequest = null;};// Handle browser request cancellation (as opposed to a manual cancellation)request.onabort = function handleAbort() {if (!request) {return;}reject(createError('Request aborted', config, 'ECONNABORTED', request));// Clean up requestrequest = null;};// Handle low level network errorsrequest.onerror = function handleError() {// Real errors are hidden from us by the browser// onerror should only fire if it's a network errorreject(createError('Network Error', config, null, request));// Clean up requestrequest = null;};// Handle timeoutrequest.ontimeout = function handleTimeout() {reject(createError('timeout of ' + config.timeout + 'ms exceeded', config, 'ECONNABORTED',request));// Clean up requestrequest = null;};// Add xsrf header// This is only done if running in a standard browser environment.// Specifically not if we're in a web worker, or react-native.if (utils.isStandardBrowserEnv()) {var cookies = require('./../helpers/cookies');// Add xsrf headervar xsrfValue = (config.withCredentials || isURLSameOrigin(config.url)) && config.xsrfCookieName ?cookies.read(config.xsrfCookieName) :undefined;if (xsrfValue) {requestHeaders[config.xsrfHeaderName] = xsrfValue;}}// Add headers to the requestif ('setRequestHeader' in request) {utils.forEach(requestHeaders, function setRequestHeader(val, key) {if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') {// Remove Content-Type if data is undefineddelete requestHeaders[key];} else {// Otherwise add header to the requestrequest.setRequestHeader(key, val);}});}// Add withCredentials to request if neededif (config.withCredentials) {request.withCredentials = true;}// Add responseType to request if neededif (config.responseType) {try {request.responseType = config.responseType;} catch (e) {// Expected DOMException thrown by browsers not compatible XMLHttpRequest Level 2.// But, this can be suppressed for 'json' type as it can be parsed by default 'transformResponse' function.if (config.responseType !== 'json') {throw e;}}}// Handle progress if neededif (typeof config.onDownloadProgress === 'function') {request.addEventListener('progress', config.onDownloadProgress);}// Not all browsers support upload eventsif (typeof config.onUploadProgress === 'function' && request.upload) {request.upload.addEventListener('progress', config.onUploadProgress);}if (config.cancelToken) {// Handle cancellationconfig.cancelToken.promise.then(function onCanceled(cancel) {if (!request) {return;}request.abort();reject(cancel);// Clean up requestrequest = null;});}if (requestData === undefined) {requestData = null;}// Send the requestrequest.send(requestData);});
};

xhrAdapter方法为浏览器环境下的核心,主要做了以下事情:

  • 1.构造XMLHttpRequest对象。
  • 2.判断是否配置用户名密码,如果有,则添加到Header中。
  • 3.通过open方法打开请求。
  • 4.设置超时时间。
  • 5.配置onreadystatechange回调方法。
  • 6.配置onabort回调方法。
  • 7.配置onerror回调方法。
  • 8.配置ontimeout回调方法。
  • 9.如果是标准的浏览器环境,则添加xsrf值。
  • 10.如果要求withCredentials,则将withCredentials设为true。
  • 11.如果配置了responseType,则配置responseType。
  • 12.如果配置了下载回调方法,则添加下载回调方法。
  • 13.如果配置了上传回调方法,则添加上传回调方法。
  • 14.判断这时是否取消了请求,如果取消请求,则取消请求。
  • 15.发起请求。

可以看到,xhrAdapter内部很贴心的为我们做了各种适配和校验。发起请求后,如果网络执行正常,那么我们的关注点应该在onreadystatechange回调方法内:

// lib/adapters/xhr.js
request.onreadystatechange = function handleLoad() {if (!request || request.readyState !== 4) {return;}// The request errored out and we didn't get a response, this will be// handled by onerror instead// With one exception: request that using file: protocol, most browsers// will return status as 0 even though it's a successful requestif (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {return;}// Prepare the responsevar responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;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};settle(resolve, reject, response);// Clean up requestrequest = null;};

onreadystatechange内部做了以下事情:

  • 1.对file:请求协议做特殊处理。
  • 2.构造响应对象。
  • 3.进入settle方法。
// lib/core/settle.js
module.exports = function settle(resolve, reject, response) {var validateStatus = response.config.validateStatus;if (!validateStatus || validateStatus(response.status)) {resolve(response);} else {reject(createError('Request failed with status code ' + response.status,response.config,null,response.request,response));}
};

settle内部允许自行判断可接收的状态,通过validateStatus方法校验。执行完这一部之后,就算执行完成了。

到此为止应该是执行刚刚xhrAdapter中所返回的Promise,我们需要回到这个Promise所对应的then方法:

// lib/core/dispatchRequest.jsreturn adapter(config).then(function onAdapterResolution(response) {throwIfCancellationRequested(config);// Transform response data// 允许自定义转换方法response.data = transformData(response.data,response.headers,config.transformResponse);return response;}, function onAdapterRejection(reason) {if (!isCancel(reason)) {throwIfCancellationRequested(config);// Transform response dataif (reason && reason.response) {reason.response.data = transformData(reason.response.data,reason.response.headers,config.transformResponse);}}return Promise.reject(reason);});

一切顺利,这里应该执行onAdapterResolution方法。onAdapterResolution方法内执行了以下事情:

  • 1.校验是否取消请求。
  • 2.对返回的对象做反序列化处理,这里同样支持自定义反序列化方法。
  • 3.返回反序列化后的对象。

到这里,我们应该又去哪里找对应的then方法呢?我们需要一层层返回。刚刚dispatchRequest方法是在lib/core/Axios.js中的request中调用的,所以这里的then方法应该还在这里。还记得之前在request中执行过的这段代码吗?

  while (chain.length) {promise = promise.then(chain.shift(), chain.shift());}

没错,由于之前chain中保存了业务网络层自定义的请求拦截器与响应拦截器,所以dispatchRequest方法会被Promise执行到业务网络层的响应拦截器中,我们在这里再贴一下业务网络层的响应拦截器:

service.interceptors.response.use(response => {return Promise.reject(response);},error => {return Promise.reject(error);}
);

嗯,我们还是什么都没处理,这里执行完,就开始触发我们的业务请求处的then方法了:

axios.get('/get/server').then(function (response) {}).catch(function (err) {});

当时我们的业务代码中什么都没写,如果通过控制台输出的话,就能看到最终的返回结果了。

到这里为止就是Axios的整个请求过程了,你是否清楚了呢?


通过阅读Axios源码,有以下总结:

  • Promise是其中的粘合剂,又做了协作的约定。
  • 面向对象的熟练使用。
  • 良好的代码规范,入参出参与分号、隔行、注释。
  • 良好的说明文档:https://www.npmjs.com/package/axios 。
  • 允许控制内部的更多细节,例如数据转换方式。

缺点:

  • 没有对高并发做处理。

前端中的其它源码分析文章:
Promise源码解析 https://sahadev.blog.csdn.net/article/details/90722543
深入解析Node.js setTimeout方法的执行过程 https://sahadev.blog.csdn.net/article/details/90703250
Vue源码探究笔记 https://sahadev.blog.csdn.net/article/details/87943168

axios网络请求框架源码解析相关推荐

  1. Anroid-async-http封装网络请求框架源码分析

    Android-async-http开源项目可以是我们轻松的获取网络数据或者向服务器发送数据,使用起来非常简单, 这个网络请求库是基于Apache HttpClient库之上的一个异步网络请求处理库, ...

  2. php 框架源码分析,Laravel框架源码解析之模型Model原理与用法解析

    本文实例讲述了Laravel框架源码解析之模型Model原理与用法.分享给大家供大家参考,具体如下: 前言 提前预祝猿人们国庆快乐,吃好.喝好.玩好,我会在电视上看着你们. 根据单一责任开发原则来讲, ...

  3. Android经典著名的百大框架源码解析(retrofit、Okhttp、Glide、Zxing、dagger等等)

    我们Android程序员每天都要和源码打交道.经过数年的学习,大多数程序员可以"写"代码,或者至少是拷贝并修改代码.而且,我们教授编程的方式强调编写代码的艺术,而不是如何阅读代码. ...

  4. php实现推广海报,php微信推广海报PHP CodeIgniter框架源码解析

    PHP CodeIgniter框架源码解析 1.index.php :入口文件 |-->define('ENVIRONMENT') |主要用于设置errors日志输出级别 |-->$sys ...

  5. BAT高级架构师合力熬夜15天,肝出了这份PDF版《Android百大框架源码解析》,还不快快码住。。。

    前言 为什么要阅读源码? 现在中高级Android岗位面试中,对于各种框架的源码都会刨根问底,从而来判断应试者的业务能力边际所在.但是很多开发者习惯直接搬运,对各种框架的源码都没有过深入研究,在面试时 ...

  6. php manual 反射,Laravel框架源码解析之反射的使用详解

    本文实例讲述了Laravel框架源码解析之反射的使用.分享给大家供大家参考,具体如下: 前言 PHP的反射类与实例化对象作用相反,实例化是调用封装类中的方法.成员,而反射类则是拆封类中的所有方法.成员 ...

  7. java 并发框架源码_Java并发编程高阶技术-高性能并发框架源码解析与实战

    Java并发编程高阶技术-高性能并发框架源码解析与实战 1 _0 Z' @+ l: s3 f6 r% t|____资料3 Z9 P- I2 x8 T6 ^ |____coding-275-master ...

  8. Android开发神器:OkHttp框架源码解析

    前言 HTTP是我们交换数据和媒体流的现代应用网络,有效利用HTTP可以使我们节省带宽和更快地加载数据,Square公司开源的OkHttp网络请求是有效率的HTTP客户端.之前的知识面仅限于框架API ...

  9. 详细讲解go web框架之gin框架源码解析记录及思路流程和理解

    开篇 首先gin 框架是在 官方提供的net/http标准包进行的相应封装. 那么要想理解gin框架, 就要先懂一些 net/http标准包 的相关知识. 可以参考中文的 文档: https://st ...

最新文章

  1. MegaCli常用命令详细介绍
  2. python 命令行参数处理 getopt模块详解
  3. 向日葵win10远程linux主机,小猪为你win10系统使用向日葵远程桌面软件远程的设置方法...
  4. CK40N成本估算错误处理
  5. android系统里面的mic是哪个app_苹果记事app哪个好用?这款便签可以跨系统使用...
  6. 前端基础HTML5CSS3动画
  7. 树莓派 红灯不亮_请问我的树莓派烧了系统后板子只有红灯亮,而act绿灯不亮,并且网口不插网线两个灯都是微微亮,请问?...
  8. 【转发】响应式Web设计?怎样进行?
  9. 华为机试——质数因子
  10. 指针系统学习5-对使用字符指针变量和字符数组的讨论
  11. python爬虫什么意思-终于知道python爬虫是什么意思
  12. 论文笔记 -- Contrastive Clustering(对比聚类)
  13. java logger 乱码_Log4j乱码
  14. 软件需求包括3个不同的层次――业务需求、用户需求和功能需求
  15. OpenGL 与显卡
  16. 怎么利用粉丝圈这个微信社区工具做好社群营销?我们是做教育行业
  17. 网管型工业交换机冗余功能介绍
  18. 五千字长文为你揭秘滴滴共享出行派单算法原理(干货)
  19. 10亿手机号如何去重?(BitMap)
  20. 深圳软件测试培训:Postman的Monitor功能

热门文章

  1. C++ 多重继承之内存存储
  2. 西安下雪了,做了一个室内温度计
  3. 美图赏析:拆解USB无线网卡,电路方案非常经典
  4. 怎么把一个bool数组转成char?
  5. C++ —— 初识C++
  6. python 存储图片 alpha_保存时Matplotlib图形面颜色alpha(背景色、透明度)
  7. python进行对应分析_机器学习算法---对应分析
  8. 指令系统——数据寻址(3)——堆栈寻址(详解)
  9. 天池 在线编程 聪明的销售(计数+贪心)
  10. LeetCode 1691. 堆叠长方体的最大高度(排序+最大上升子序DP)