项目主要技术栈是Vue,因此咱们在请求后端接口的时候通常会用到axios的组件,那么咱们就来分析一下它的源码及它的实现。javascript
axios主要目录结构
├── /dist/ # 项目输出目录 ├── /lib/ # 项目源码目录 │ ├── /cancel/ # 定义取消功能 │ ├── /core/ # 一些核心功能 │ │ ├── Axios.js # axios的核心主类---------------------------这是其最核心部分 │ │ ├── dispatchRequest.js # 用来调用http请求适配器方法发送请求 | │ │ ├── InterceptorManager.js # 拦截器构造函数 | │ │ └── settle.js # 根据http响应状态,改变Promise的状态-------- │ ├── /helpers/ # 一些辅助方法 │ ├── /adapters/ # 定义请求的适配器 xhr、http----这个文件夹封装了ajax的请求 │ │ ├── http.js # 实现http适配器 │ │ └── xhr.js # 实现xhr适配器 │ ├── axios.js # 对外暴露接口 │ ├── defaults.js # 默认配置 │ └── utils.js # 公用工具 ├── package.json # 项目信息 ├── index.d.ts # 配置TypeScript的声明文件 └── index.js # 入口文件
由使用到探索
咱们通常使用的时候都会先new一个axios实例出来,那么这个实例是怎么来的呢?里边都有什么呢?咱们来看一下源代码最外层一个axios.js文件java
function createInstance(defaultConfig) { var context = new Axios(defaultConfig); var instance = bind(Axios.prototype.request, context); // Copy axios.prototype to instance utils.extend(instance, Axios.prototype, context); // Copy context to instance utils.extend(instance, context); return instance; } // Create the default instance to be exported var axios = createInstance(defaults); // 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)); };
经过源代码咱们能够看到axios导出的axios对象中有一个create方法,还有个Axios对象,同时axios自己仍是createInstance函数,而且他们都会传递一个参数进去,因此咱们在使用axios的时候会有多种不一样的调用方法,他们最终实例化的实际上是core文件下的Axios.js中所写的Axios构造函数。ios
// /lib/core/Axios.js function Axios(instanceConfig) { this.defaults = instanceConfig; this.interceptors = { request: new InterceptorManager(), response: new InterceptorManager() }; } Axios.prototype.request = function request(config) { // ...省略代码 }; // 为支持的请求方法提供别名 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 })); }; });
上边是部分源代码,咱们能够看到在这个构造函数中他会有个default对象,是用来存放咱们的配置信息的,还有个interceptors对象,里边包了两个实例,分别对应了request拦截器和response拦截器。而且在Axios的原型链上有写了一个request方法,并对支持的请求方法都赋值了一个别名。ajax
咱们的配置config是如何生效的
咱们看源代码会发现几乎每一个方法都会有一个config参数,咱们使用的时候的config就是经过这种方式贯穿整个axios的。咱们来看一下咱们使用的3种方式:json
import axios from 'axios' // 第1种:直接修改Axios实例上defaults属性,主要用来设置通用配置 axios.defaults[configName] = value; // 第2种:发起请求时最终会调用Axios.prototype.request方法,而后传入配置项,主要用来设置“个例”配置 axios({ url, method, headers, }); // 第3种:新建一个Axios实例,传入配置项,此处设置的是通用配置 let newAxiosInstance = axios.create({ [configName]: value, });
对比咱们刚才所说的axios实例的生成,咱们能够看到这三种方式其实都会把config用过参数的形式传递进去,而且在每一步调用的时候生效。axios
请求拦截器和响应拦截器
咱们在上边看axios的构造函数的时候看到他绑定了一个request拦截器和response拦截器,那么他们是作什么的呢?又是如何生效的呢?顾名思义,拦截器就是拦截请求的,能够在发送request以前或者接收response以前进行拦截,而后作一些事情达到咱们的目的。后端
// /lib/core/InterceptorManager.js function InterceptorManager() { this.handlers = []; // 存放拦截器方法,数组内每一项都是有两个属性的对象,两个属性分别对应成功和失败后执行的函数。 } // 往拦截器里添加拦截方法 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.prototype.request = function request(config) { // ... var chain = [dispatchRequest, undefined]; // 初始化一个promise对象,状态微resolved,接收到的参数微config对象 var promise = Promise.resolve(config); // 注意:interceptor.fulfilled 或 interceptor.rejected 是可能为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); }); // 添加了拦截器后的chain数组大概会是这样的: // [ // requestFulfilledFn, requestRejectedFn, ..., // dispatchRequest, undefined, // responseFulfilledFn, responseRejectedFn, ...., // ] // 只要chain数组长度不为0,就一直执行while循环 while (chain.length) { // 数组的 shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。 // 每次执行while循环,从chain数组里按序取出两项,并分别做为promise.then方法的第一个和第二个参数 // 按照咱们使用InterceptorManager.prototype.use添加拦截器的规则,正好每次添加的就是咱们经过InterceptorManager.prototype.use方法添加的成功和失败回调 // 经过InterceptorManager.prototype.use往拦截器数组里添加拦截器时使用的数组的push方法, // 对于请求拦截器,从拦截器数组按序读到后是经过unshift方法往chain数组数里添加的,又经过shift方法从chain数组里取出的,因此得出结论:对于请求拦截器,先添加的拦截器会后执行 // 对于响应拦截器,从拦截器数组按序读到后是经过push方法往chain数组里添加的,又经过shift方法从chain数组里取出的,因此得出结论:对于响应拦截器,添加的拦截器先执行 // 第一个请求拦截器的fulfilled函数会接收到promise对象初始化时传入的config对象,而请求拦截器又规定用户写的fulfilled函数必须返回一个config对象,因此经过promise实现链式调用时,每一个请求拦截器的fulfilled函数都会接收到一个config对象 // 第一个响应拦截器的fulfilled函数会接受到dispatchRequest(也就是咱们的请求方法)请求到的数据(也就是response对象),而响应拦截器又规定用户写的fulfilled函数必须返回一个response对象,因此经过promise实现链式调用时,每一个响应拦截器的fulfilled函数都会接收到一个response对象 // 任何一个拦截器的抛出的错误,都会被下一个拦截器的rejected函数收到,因此dispatchRequest抛出的错误才会被响应拦截器接收到。 // 由于axios是经过promise实现的链式调用,因此咱们能够在拦截器里进行异步操做,而拦截器的执行顺序仍是会按照咱们上面说的顺序执行,也就是 dispatchRequest 方法必定会等待全部的请求拦截器执行完后再开始执行,响应拦截器必定会等待 dispatchRequest 执行完后再开始执行。 promise = promise.then(chain.shift(), chain.shift()); } return promise; };
dispatchrequest作了什么事情
咱们再看拦截器生效的时候发如今Axios.prototype.request
中会有这么一段代码var chain = [dispatchRequest, undefined];
,而且会在promise = promise.then(chain.shift(), chain.shift());
被调用,那么这个dispatchrequest是什么呢?数组
// /lib/core/dispatchRequest.js module.exports = function dispatchRequest(config) { throwIfCancellationRequested(config); // Support baseURL config if (config.baseURL && !isAbsoluteURL(config.url)) { config.url = combineURLs(config.baseURL, config.url); } // Ensure headers exist config.headers = config.headers || {}; // 对请求data进行转换 config.data = transformData( config.data, config.headers, config.transformRequest ); // 对header进行合并处理 config.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]; } ); // http请求适配器会优先使用config上自定义的适配器,没有配置时才会使用默认的XHR或http适配器,不过大部分时候,axios提供的默认适配器是可以知足咱们的 var adapter = config.adapter || defaults.adapter; return adapter(config).then(/**/); };
经过源码分析,咱们能够看到dispatchrequest主要作了3件事情:promise
-
拿到config对象,对config进行传给http请求适配器前的最后处理;异步
-
http请求适配器根据config配置,发起请求 ;
-
http请求适配器请求完成后,若是成功则根据header、data、和config.transformResponse拿到数据,
转换后的response,并return。
段落小结
经过上面对axios源码结构分析,咱们能够获得axios搭建的一个主要结构:
调用===>Axios.prototype.request===>请求拦截器interceptors===>dispatchRequest===>请求转换器transformRequest===>请求适配器xhrAdapter===>响应转换器transformResponse===>响应拦截器interceptors
axios如何基于promise搭建异步桥梁
了解了axios的主要结构及调用顺序,那咱们来看一下axios是如何经过promise来搭建异步的桥梁?
axios.get(/**/) .then(data => { // 此处能够拿到向服务端请求回的数据 }); .catch(error => { // 此处能够拿到请求失败或取消或其余处理失败的错误对象 });
咱们那get方法来举例,当咱们调用axios的get方法的时候其实是调用axios原型链上的get方法,而其原型链上的get方法又指向了其原型链上的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 })); }; });
Axios.prototype.request = function request(config) { // ... var chain = [dispatchRequest, undefined]; // 将config对象看成参数传给Primise.resolve方法 var promise = Promise.resolve(config); while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); } return promise; };
咱们能够看到在request方法里边有个chain数组,这个实际上是至关于一个方法的队列,这里会把request的拦截器插入chain数组的前边,response拦截器插入chain数组后边,经过下边的while循环,两两调用promise.then来顺序执行请求拦截器到dispatchrequest再到响应拦截器的方法。
在dispatchrequest中呢又会调用封装好的ajax(xhrAdapter方法),xhrAdapter
方法返回的是还一个Promise对象
// /lib/adapters/xhr.js function xhrAdapter(config) { return new Promise(function dispatchXhrRequest(resolve, reject) { // ... 省略代码 }); };
xhrAdapter
内的XHR发送请求成功后会执行这个Promise对象的resolve
方法,并将请求的数据传出去, 反之则执行reject
方法,并将错误信息做为参数传出去。