axios源码分析 - XHR篇
文章源码托管在github上,欢迎fork指正!
axios 是一个基于 Promise 的http请求库,可以用在浏览器和node.js中,目前在github上有 42K 的star数
备注:
- 每一小节都会从两个方面介绍:如何使用 -> 源码分析
- [工具方法简单介绍]一节可先跳过,后面用到了再过来查看
- axios最核心的技术点是如何拦截请求响应并修改请求参数修改响应数据 和 axios是如何用promise搭起基于xhr的异步桥梁的
axios项目目录结构
1 | 复制代码 |
注:因为我们需要要看的代码都是/lib/
目录下的文件,所以以下所有涉及到文件路径的地方,
我们都会在/lib/
下进行查找
名词解释
- 拦截器 interceptors
(如果你熟悉中间件,那么就很好理解了,因为它起到的就是基于promise的中间件的作用)
拦截器分为请求拦截器和响应拦截器,顾名思义:
请求拦截器(interceptors.request
)是指可以拦截住每次或指定http请求,并可修改配置项
响应拦截器(interceptors.response
)可以在每次http请求后拦截住每次或指定http请求,并可修改返回结果项。
这里先简单说明,后面会做详细的介绍如何拦截请求响应并修改请求参数修改响应数据。
- 数据转换器 (其实就是对数据进行转换,比如将对象转换为JSON字符串)
数据转换器分为请求转换器和响应转换器,顾名思义:
请求转换器(transformRequest
)是指在请求前对数据进行转换,
响应转换器(transformResponse
)主要对请求响应后的响应体做数据转换。
- http请求适配器(其实就是一个方法)
在axios项目里,http请求适配器主要指两种:XHR、http。
XHR的核心是浏览器端的XMLHttpRequest对象,
http核心是node的http[s].request方法
当然,axios也留给了用户通过config自行配置适配器的接口的,
不过,一般情况下,这两种适配器就能够满足从浏览器端向服务端发请求或者从node的http客户端向服务端发请求的需求。
本次分享主要围绕XHR。
- config配置项 (其实就是一个对象)
此处我们说的config,在项目内不是真的都叫config这个变量名,这个名字是我根据它的用途起的一个名字,方便大家理解。
在axios项目中的,设置\读取config时,
有的地方叫它defaults
(/lib/defaults.js
),这儿是默认配置项,
有的地方叫它config
,如Axios.prototype.request
的参数,再如xhrAdapter
http请求适配器方法的参数。
config在axios项目里的是非常重要的一条链,是用户跟axios项目内部“通信”的主要桥梁。
axios内部的运作流程图
工具方法简单介绍
(注:本节可先跳过,后面用到了再过来查看)
有一些方法在项目中多处使用,简单介绍下这些方法
- bind: 给某个函数指定上下文,也就是this指向
1 | 复制代码 |
实现效果同Function.prototype.bind
方法: fn.bind(context)
- forEach:遍历数组或对象
1 | 复制代码 |
- merge:深度合并多个对象为一个对象
1 | 复制代码 |
mergedObj对象是:
1 | 复制代码 |
- extend:将一个对象的方法和属性扩展到另外一个对象上,并指定上下文
1 | 复制代码 |
extendObj对象是:
1 | 复制代码 |
执行extendObj.fn();
, 打印3
axios为何会有多种使用方式
如何使用
1 | 复制代码 |
第1种使用方式:axios(option)
1 | 复制代码 |
第2种使用方式:axios(url[, option])
1 | 复制代码 |
第3种使用方式(对于get、delete
等方法):axios[method](url[, option])
1 | 复制代码 |
第4种使用方式(对于post、put
等方法):axios[method](url[, data[, option]])
1 | 复制代码 |
第5种使用方式:axios.request(option)
1 | 复制代码 |
源码分析
作为axios项目的入口文件,我们先来看下axios.js
的源码
能够实现axios的多种使用方式的核心是createInstance
方法:
1 | 复制代码 |
以上代码看上去很绕,其实createInstance
最终是希望拿到一个Function,这个Function指向Axios.prototype.request
,这个Function还会有Axios.prototype
上的每个方法作为静态方法,且这些方法的上下文都是指向同一个对象。
那么在来看看Axios、Axios.prototype.request
的源码是怎样的?
Axios
是axios包的核心,一个Axios
实例就是一个axios应用,其他方法都是对Axios
内容的扩展
而Axios
构造函数的核心方法是request
方法,各种axios的调用方式最终都是通过request
方法发请求的
1 | 复制代码 |
通过以上代码,我们就可以以多种方式发起http请求了: axios()、axios.get()、axios.post()
一般情况,项目使用默认导出的axios实例就可以满足需求了,
如果不满足需求需要创建新的axios实例,axios包也预留了接口,
看下面的代码:
1 | 复制代码 |
说完axios为什么会有这么多种使用方式,可能你心中会有一个疑问:
使用axios时,无论get
方法还是post
方法,最终都是调用的Axios.prototype.request
方法,那么这个方法是怎么根据我们的config配置发请求的呢?
在开始说Axios.prototype.request
之前,我们先来捋一捋在axios项目中,用户配置的config是怎么起作用的?
用户配置的config是怎么起作用的
这里说的config
,指的是贯穿整个项目的配置项对象,
通过这个对象,可以设置:
http请求适配器、请求地址、请求方法、请求头header、 请求数据、请求或响应数据的转换、请求进度、http状态码验证规则、超时、取消请求等
可以发现,几乎axios
所有的功能都是通过这个对象进行配置和传递的,
既是axios
项目内部的沟通桥梁,也是用户跟axios
进行沟通的桥梁。
首先我们看看,用户能以什么方式定义配置项:
1 | 复制代码 |
看下 Axios.prototype.request
方法里的一行代码: (/lib/core/Axios.js
- 第35行)
1 | 复制代码 |
可以发现此处将默认配置对象defaults
(/lib/defaults.js
)、Axios实例属性this.defaults
、request
请求的参数config
进行了合并。
由此得出,多处配置的优先级由低到高是:
—> 默认配置对象defaults
(/lib/defaults.js
)
—> { method: ‘get’ }
—> Axios实例属性this.defaults
—> request
请求的参数config
留给大家思考一个问题: defaults
和 this.defaults
什么时候配置是相同的,什么时候是不同的?
至此,我们已经得到了将多处merge
后的config
对象,那么这个对象在项目中又是怎样传递的呢?
1 | 复制代码 |
至此,config
走完了它传奇的一生 -_-
下一节就要说到重头戏了: Axios.prototype.request
axios.prototype.request
这里面的代码比较复杂,一些方法需要追根溯源才能搞清楚,
所以只需对chain数组有个简单的了解就好,涉及到的拦截器、[dispatchRequest
]后面都会详细介绍
chain
数组是用来盛放拦截器方法和dispatchRequest
方法的,
通过promise从chain
数组里按序取出回调函数逐一执行,最后将处理后的新的promise在Axios.prototype.request
方法里返回出去,
并将response或error传送出去,这就是Axios.prototype.request
的使命了。
查看源码:
1 | 复制代码 |
此时,你一定对拦截器充满了好奇,这个拦截器到底是个什么家伙,下一节就让我们一探究竟吧
如何拦截请求响应并修改请求参数修改响应数据
如何使用
1 | 复制代码 |
思考
- 是否可以直接 return error?
1 | 复制代码 |
- 如何实现promise的链式调用
1 | 复制代码 |
源码分析
关于拦截器,名词解释一节已经做过简单说明。
每个axios实例都有一个interceptors
实例属性,interceptors
对象上有两个属性request
、response
。
1 | 复制代码 |
这两个属性都是一个InterceptorManager
实例,而这个InterceptorManager
构造函数就是用来管理拦截器的。
我们先来看看InterceptorManager
构造函数:
InterceptorManager
构造函数就是用来实现拦截器的,这个构造函数原型上有3个方法:use、eject、forEach。
关于源码,其实是比较简单的,都是用来操作该构造函数的handlers实例属性的。
1 | 复制代码 |
那么当我们通过axios.interceptors.request.use
添加拦截器后,
axios内部又是怎么让这些拦截器能够在请求前、请求后拿到我们想要的数据的呢?
先看下代码:
1 | 复制代码 |
现在,你应该已经清楚了拦截器是怎么回事,以及拦截器是如何在Axios.prototype.request
方法里发挥作用的了,
那么处于”中游位置”的dispatchRequest
是如何发送http请求的呢?
dispatchrequest都做了哪些事
dispatchRequest主要做了3件事:
1,拿到config对象,对config进行传给http请求适配器前的最后处理;
2,http请求适配器根据config配置,发起请求
3,http请求适配器请求完成后,如果成功则根据header、data、和config.transformResponse(关于transformResponse,下面的数据转换器会进行讲解)拿到数据转换后的response,并return。
1 | 复制代码 |
好了,看到这里,我们是时候梳理一下:axios是如何用promise搭起基于xhr的异步桥梁的?
axios是如何用promise搭起基于xhr的异步桥梁的
axios是如何通过Promise进行异步处理的?
如何使用
1 | 复制代码 |
源码分析
先来一个图简单的了解下axios项目里,http请求完成后到达用户的顺序流:
通过axios为何会有多种使用方式我们知道,
用户无论以什么方式调用axios,最终都是调用的Axios.prototype.request
方法,
这个方法最终返回的是一个Promise对象。
1 | 复制代码 |
Axios.prototype.request
方法会调用dispatchRequest
方法,而dispatchRequest
方法会调用xhrAdapter
方法,xhrAdapter
方法返回的是还一个Promise对象
1 | 复制代码 |
xhrAdapter
内的XHR发送请求成功后会执行这个Promise对象的resolve
方法,并将请求的数据传出去,
反之则执行reject
方法,并将错误信息作为参数传出去。
1 | 复制代码 |
验证服务端的返回结果是否通过验证:
1 | 复制代码 |
回到dispatchRequest
方法内,首先得到xhrAdapter
方法返回的Promise对象,
然后通过.then
方法,对xhrAdapter
返回的Promise对象的成功或失败结果再次加工,
成功的话,则将处理后的response
返回,
失败的话,则返回一个状态为rejected
的Promise对象,
1 | 复制代码 |
那么至此,用户调用axios()
方法时,就可以直接调用Promise的.then
或.catch
进行业务处理了。
回过头来,我们在介绍dispatchRequest
一节时说到的数据转换,而axios官方也将数据转换专门作为一个亮点来介绍的,那么数据转换到底能在使用axios发挥什么功效呢?
数据转换器-转换请求与响应数据
如何使用
- 修改全局的转换器
1 | 复制代码 |
- 修改某次axios请求的转换器
1 | 复制代码 |
源码分析
默认的defaults
配置项里已经自定义了一个请求转换器和一个响应转换器,
看下源码:
1 | 复制代码 |
那么在axios项目里,是在什么地方使用了转换器呢?
请求转换器的使用地方是http请求前,使用请求转换器对请求数据做处理,
然后传给http请求适配器使用。
1 | 复制代码 |
看下transformData
方法的代码,
主要遍历转换器数组,分别执行每一个转换器,根据data和headers参数,返回新的data。
1 | 复制代码 |
响应转换器的使用地方是在http请求完成后,根据http请求适配器的返回值做数据转换处理:
1 | 复制代码 |
转换器和拦截器的关系?
拦截器同样可以实现转换请求和响应数据的需求,但根据作者的设计和综合代码可以看出,
在请求时,拦截器主要负责修改config配置项,数据转换器主要负责转换请求体,比如转换对象为字符串
在请求响应后,拦截器可以拿到response
,数据转换器主要负责处理响应体,比如转换字符串为对象。
axios官方是将”自动转换为JSON数据”作为一个独立的亮点来介绍的,那么数据转换器是如何完成这个功能的呢?
其实非常简单,我们一起看下吧。
自动转换json数据
在默认情况下,axios将会自动的将传入的data对象序列化为JSON字符串,将响应数据中的JSON字符串转换为JavaScript对象
源码分析
1 | 复制代码 |
至此,axios项目的运作流程已经介绍完毕,是不是已经打通了任督二脉了呢
接下来我们一起看下axios还带给了我们哪些好用的技能点吧。
header设置
如何使用
1 | 复制代码 |
源码分析
1 | 复制代码 |
如何取消已经发送的请求
如何使用
1 | 复制代码 |
源码分析
1 | 复制代码 |
取消功能的核心是通过CancelToken内的this.promise = new Promise(resolve => resolvePromise = resolve)
,
得到实例属性promise
,此时该promise
的状态为pending
通过这个属性,在/lib/adapters/xhr.js
文件中继续给这个promise
实例添加.then
方法
(xhr.js
文件的159行config.cancelToken.promise.then(message => request.abort())
);
在CancelToken
外界,通过executor
参数拿到对cancel
方法的控制权,
这样当执行cancel
方法时就可以改变实例的promise
属性的状态为rejected
,
从而执行request.abort()
方法达到取消请求的目的。
上面第二种写法可以看作是对第一种写法的完善,
因为很多是时候我们取消请求的方法是用在本次请求方法外,
例如,发送A、B两个请求,当B请求成功后,取消A请求。
1 | 复制代码 |
相对来说,我更推崇第1种写法,因为第2种写法太隐蔽了,不如第一种直观好理解。
发现的问题
- /lib/adapters/xhr.js文件中,onCanceled方法的参数不应该叫message么,为什么叫cancel?
- /lib/adapters/xhr.js文件中,onCanceled方法里,reject里应该将config信息也传出来
跨域携带cookie
如何使用
1 | 复制代码 |
源码分析
我们在用户配置的config是怎么起作用的一节已经介绍了config在axios项目里的传递过程,
由此得出,我们通过axios.defaults.withCredentials = true
做的配置,
在/lib/adapters/xhr.js
里是可以取到的,然后通过以下代码配置到xhr对象项。
1 | 复制代码 |
超时配置及处理
如何使用
1 | 复制代码 |
源码分析
1 | 复制代码 |
- axios库外如何添加超时后的处理
1 | 复制代码 |
改写验证成功或失败的规则validatestatus
自定义http状态码的成功、失败范围
如何使用
1 | 复制代码 |
源码分析
在默认配置中,定义了默认的http状态码验证规则,
所以自定义validateStatus
其实是对此处方法的重写
1 | 复制代码 |
axios是何时开始验证http状态码的?
1 | 复制代码 |
1 | 复制代码 |
总结
axios这个项目里,有很多对JS使用很巧妙的地方,比如对promise的串联操作(当然你也可以说这块是借鉴很多异步中间件的处理方式),让我们可以很方便对请求前后的各种处理方法的流程进行控制;很多实用的小优化,比如请求前后的数据处理,省了程序员一遍一遍去写JSON.xxx了;同时支持了浏览器和node两种环境,对使用node的项目来说无疑是极好的。
总之,这个能够在github斩获42K+(截止2018.05.27)的star,实力绝不是盖的,值得好好交交心!
本文转载自: 掘金