花了两天时间读了一遍axios源码,终于理解了axios内部是怎么运行的,这期间学习到了很多,所以想分享一下。想了很久,决定用一些问题来作为引子,一点点的整理一下我对axios源码的理解。下面我会用到一些官方文档的用例来说明。
用过axios的童鞋应该都知道,axios有很多请求的api,用起来是非常方便的。那么大家知道axios有多少种方法方式可以发起请求吗?为什么这些方法方式都可以发起请求?内部是怎么实现的。
这里我会先整理一下有哪些方法api,然后最后来整理一下源码中是如果实现这些方法的。
axios发起请求的方法
我们现在说说有哪些方法。首先要说明一下,我们一般都是这样引入axios的:
1 | import axios from 'axios' |
axios()
axios()
是可以直接作为函数调用的。本身就是一个请求函数,可以直接传入请求的url,请求数据及请求配置。
1 | axios({ |
其实内部相当于调用了createInstance(config)
方法。
axios.create()
这个方法不是能直接发起请求的,而是创建一个Axios实例,然后在通过实例的request()
方法或者其他实例上的方法去发起请求。
1 | // Create an instance using the config defaults provided by the library |
axios[method]()
这个方法比较直观,根据请求的method
,可以选择对应的请求方法,比如get请求可以使用axios.get
,post请求可以使用axios.post
。
1 | axios.get('/user/12345') |
这些方法其实都是封装了axios.request()
方法的,只是把传入的参数中method
固定下来。
new axios.Axios
这个也是用来创建axios实例的,然后再用axios实例去调方法实现调起请求。
axios.request()
request()
是axios中最重要的方法之一,上面所有的调起请求的方式都是基于这个方法,也是我们下面学习源码最先解读的内容。
内部实现
关键函数request()的实现
其实request()
方法的源码非常少,很容易理解。主要分成两个部分,第一部分是整理合并config
,第二部分是将拦截器的函数入栈chain
,然后用Promise
来执行。
这两个部分虽然没有提到发请求,但显而易见的是用Promise
来执行时肯定是发了请求的。我们一步步来解读。
首先,第一部分是整理合并config
。在这里,请求的配置除了传参进来的config
之外,还有在axios.defaults
中的配置。所以,需要先拿到config
,然后跟axios.defaults
得到本次请求的配置。
request()
方法的源码中,对config
做了统一格式的处理。因为我们知道request()
方法的传参其实支持不止一个。
1 | // 第一种传参方式 |
所以会先判断config
是否是String
类型,如果是,则判断为传入url
,第二个传参才是config
,否则第一个传参就是config
。同时也要判断没有传config
的可能
1 | // axios.request 源码 |
然后就可以合并请求配置了。源码中使用了mergeConfig()
这个方法来处理两个配置对象的合并。这个方法内部其实是针对axios的所有配置做了专门的处理。
1 | config = mergeConfig(this.defaults, config); |
合并配置方法mergeConfig()
这个方法传参只有两个,config1
和config2
。内部处理的流程是声明了三类配置属性的数组valueFromConfig2Keys
, mergeDeepPropertiesKeys
, defaultToConfig2Keys
,然后针对这三类做针对性的浅拷贝,深拷贝,默认值处理,接着再处理除这三类配置属性外其他配置属性的赋值,最后返回最终完整配置对象。
从整个处理流程来说,我认为方法中主要处理的三类配置属性应该是比较主要的配置,而且因为各自配置的不同需要分别处理。
1 | // axios mergeConfig 源码 |
valueFromConfig2Keys
这类比较简单,但需要用户提供的,直接赋值即可:
1 | // axios mergeConfig 源码 |
mergeDeepPropertiesKeys
这类配置的值类型可能是对象,需要做深拷贝:
1 | // axios mergeConfig 源码 |
defaultToConfig2Keys
这类配置是已有默认配置,所以可以使用默认值赋值。但内部还是做了undefined
的判断,因为axios.default
的配置值用户是可以修改的:
1 | // axios mergeConfig 源码 |
最后的部分就是集合三个分类的配置属性,去筛选出不在这其中的用户配置属性,再去处理赋值,只做浅拷贝。这些配置可能用户也会在axios.default
上设置默认值,所以也要考虑处理默认值:
1 | // axios mergeConfig 源码 |
处理拦截器及请求
在这之前,先处理了请求的method
,如果用户有传就将值改全小写,都没有就默认为get
。
接下来的部分我认为很精巧。在调请求之前,因为axios还支持请求拦截器跟响应拦截器,所以这里使用类栈的方案。
先设置一个数组,这个数组默认有两个元素,一个是dispatchRequest
,就是发送请求的方法,另一个是undefined
。然后遍历请求拦截器,把其中的两个函数从数组的头部插入,再遍历响应拦截器,把两个函数从数组的尾部插入。这样一来,数组的结构就是从头到尾为就是按照请求的过程排好了请求拦截器一对对函数,然后是请求函数跟undefined
,然后是响应拦截器一对对函数。
然后声明一个Promise.resolve()
后返回的promise
, 把数组中从头部开始一对对函数的传入promise.then()
中。
这个操作可以利用promise.then()
的特点,自动通过promise链去调用方法。如果请求拦截器中有报错,则最后不会调dispatchRequest
方法并断掉。
1 | // axios.request 源码 |
最后返回这个promise
使得我们调请求时可以用promise的方法继续处理。
其他请求方法的实现
那么request()
这个方法是怎么演变出这么多种调用方式的呢?
首先,在Axios
这个类下,提供了一堆axios[method]()
的方法,作者是通过两个forEach
来实现的:
1 | // Axios 源码 |
然后引入的axios
,实际是先调用了createInstance()
方法,这个方法里声明一个instance
的变量,通过bind
方法得到了一个上下文为Axios
实例的Axios.prototype.request()
方法,最后返回instance
。所以我们可以直接调用axios
方法发请求。
1 | // axios 源码 |
而axios.create()
方法内部就是直接使用了createInstance()
, axios.Axios
就是直接暴露Axios
类。
结尾
关于axios请求的源码分析就整理完了,下一篇会来了解一下axios是怎么实现请求取消的。