axios 源码解析
axios 源码中几个关键点的解析。版本号 v0.26.0
在 axios 到达 1.0
之前,所有的 breaking changes 都会以 minor
版本发布。官方说明
1. 1 axios 为何会有多种使用方式
1.1 使用方式
- 第一种,直接调用
axios(options)
- 第二种,快捷方式调用
axios.get(url, options)
axios.post(url, data, options)
- 第三种,调用 request 方法
axios.request(options)
1.2 源代码
代码路径:
/lib/axios.js
function createInstance(defaultConfig) {
// 创建一个 Axios 实例对象。
// 注意这个实例并不是最后导出的 axios !!!
// 这个对象只是用作绑定 this。各个请求之间能共享属性(如:拦截器,配置参数等)也都是因为指向这同一个 this 来完成的
var context = new Axios(defaultConfig)
// 把 Axios 原型对象上的 request 绑定 this 后,作为最终返回的请求方法
var instance = bind(Axios.prototype.request, context)
// 复制 Axios 原型对象上的方法(如:get、post、delete、put 等等),并绑定 this
utils.extend(instance, Axios.prototype, context)
// 复制 Axios 实例对象上的属性(如:defaults、interceptors),并绑定 this
utils.extend(instance, context)
// 最终返回的是 request 函数
// 通过把其他请求方法复制到 request 函数对象上,来达到最终多种调用方式的目的
return instance
}
var axios = createInstance(defaults)
流程: 每次调用 createInstance
都会创建一个新的上下文实例,然后把这个上下文实例绑定为 request
的 this
,再通过复制的方式把其他的方法、属性等等挂载到这个 request
上。最后返回这个请求函数对象 request
。
用一段伪代码来表示最终效果:
// 定义
function axios() {}
axios.get = function () {}
axios.post = function () {}
axios.request = function () {}
axios.defaults = {}
axios.interceptors = {
request,
response,
}
// 使用
axios(options)
axios.get()
2. 2 config 配置方式
以配置 baseURL
、timeout
为例演示说明。
2.1 axios 的默认配置
代码路径
/lib/axios.js
// /lib/defaults.js
var defaults = require('./defaults')
function createInstance(defaultConfig) {
// ...
var context = new Axios(defaultConfig)
// ...
return instance
}
var axios = createInstance(defaults)
代码路径
/lib/core/Axios.js
function Axios(instanceConfig) {
this.defaults = instanceConfig
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
}
}
以上是几个相关的代码片段。可以看到:
-
导出的
axios
其实也是通过createInstance
生成的,此时的配置参数为axios
的默认配置defaults
。 -
createInstance
将配置保存在axios
的属性defaults
上。
2.2 直接修改 axios 上的属性
使用方式
axios.defaults['baseURL'] = 'http://somesite.com'
axios.defaults['timeout'] = 8000
在了解了 axios
这个函数对象上有 defaults
这个属性后,这种方式就很好理解了,
就是直接修改 axios
上的 defaults
对象
2.3 在请求的时候直接传递配置
使用方式
axios.post(url, data, {
baseURL: 'http://somesite.com',
timeout: 10000,
})
源代码
代码路径
/lib/core/Axios
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
Axios.prototype[method] = function (url, data, config) {
return this.request(
mergeConfig(config || {}, {
method: method,
url: url,
data: data,
}),
)
}
})
Axios.prototype.request = function request(config) {
// ...
config = mergeConfig(this.defaults, config)
// ...
}
需要再次强调的是,虽然这里看到的方法都是 Axios
原型对象上的,但是实际都是在 createInstance
中被复制到了 axios
这个函数对象上,并且绑定了 this
为 axios
。
所以看到这里,思路也很清晰了。在 post
中,实际就是把自身快捷方式的一些属性通过 mergeConfig
合并到传入的配置对象 config
中,然后传递执行真正的请求方法 request
。
request
又会把传进来的 config
和 defaults
进行合并,得到最终的请求配置
2.4 新的请求实例重新配置
使用方式
const instance = axios.create({
baseURL: 'http://somesite.com',
timeout: 12000,
})
instance.defaults['baseURL'] = 'http://somesite.com'
instance.defaults['timeout'] = 14000
instance.post(url, data, {
baseURL: 'http://somesite.com',
timeout: 16000,
})
源代码
实例上修改 defaults
以及在 post
中传入请求配置,原理和 axios
是一样的。
剩下就是来看下 axios.create
做了什么特别的事情。
代码路径
/lib/axios
function createInstance(defaultConfig) {
// ...
instance.create = function create(instanceConfig) {
return createInstance(mergeConfig(defaultConfig, instanceConfig))
}
return instance
}
这里尤其需要注意的是,create
的配置参数不是直接传递给 createInstance
,而是先和 axios.defaults
进行了合并,然后在传递下去使用。
这也就意味着,通过 axios.create
创建的实例,都是会继承 axios.defaults
上的配置。
所以在平时开发中,通过 axios.defaults
直接修改配置时,需要谨慎判断,这个属性是否真的有必要被项目中其他实例继承
2.4 配置优先级
了解了几种请求配置的方式后,最后梳理下几种方式产生配置的优先级,分两种情况,也就是 axios
以及 axios 实例
axios 直接请求
优先级从高到低依次为
-
一些快捷方式特定的配置。如 post 请求,
{ method: 'post' }
-
post options。一次性的,不会影响后续请求
-
axios 上的 defaults 属性
-
全局默认配置对象
/lib/defaults
axios 实例 instance 发起的请求
优先级从高到低依次为
-
一些快捷方式特定的配置。如 post 请求,
{ method: 'post' }
-
post options。一次性的,不会影响后续请求
-
通过 axios.create 传入的配置
-
axios 上的 defaults 属性
-
全局默认 配置对象
/lib/defaults
3. 3 请求及请求拦截器的工作流程
先用一段伪代码来表示最终效果
// 拦截器配置
axios.interceptors.request.use(request11, request12)
axios.interceptors.request.use(request21, request22)
axios.interceptors.response.use(response31, response32)
axios.interceptors.response.use(response41, response42)
// 最终执行顺序
Promise.resolve(config)
// request 倒序执行
.then(request21, request22)
.then(request11, request12)
.then(dispatchRequest)
// response 顺序执行
.then(response31, response32)
.then(response41, response42)