深海鱼的博客 深海鱼的博客
首页
  • 《ES6 教程》
  • 《TypeScript》
  • 《Vue》
  • 《React》
  • 《Git》
  • Javascript
  • CSS
手写系列
  • React项目实战
  • Vue3项目实战
  • 服务端项目实战
  • 鸿蒙项目实战
  • 小小实践
  • Vue全家桶

    • Vue2.0
    • Vue3.0
    • VueRouter
    • Vuex
  • React

    • React
  • Axios
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

深海鱼

尽人事,知天命,知行合一。
首页
  • 《ES6 教程》
  • 《TypeScript》
  • 《Vue》
  • 《React》
  • 《Git》
  • Javascript
  • CSS
手写系列
  • React项目实战
  • Vue3项目实战
  • 服务端项目实战
  • 鸿蒙项目实战
  • 小小实践
  • Vue全家桶

    • Vue2.0
    • Vue3.0
    • VueRouter
    • Vuex
  • React

    • React
  • Axios
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 导读
  • vue2

  • vuex

  • vue-router

  • vue3

  • react

  • Axios

    • Axios源码解读
      • Axios导出
      • axios调用
      • dispatchRequest(config)函数
      • 总结
  • 源码解读
  • Axios
深海鱼
2024-07-29
目录

Axios源码解读

提示

本章节基于Axios 1.7.2版本撰写的源码解读,该版本是写该文章时Axios的最新版本

源码的所有注释已完成,点击这里查看:《Axios 1.7.2源码解读》 (opens new window)

逐行分析源码的文章正在撰写中~

解读文章正在撰写中~~

# Axios导出

Axios导出的内容列表如下:

  • axios:默认导出,是一个Axios实例
  • Axios:Axios类
  • AxiosError:Axios错误类,用于创建Axios相关的错误对象
  • CanceledError:请求取消错误类
  • isCancel:用于判断某个对象是否是一个CanceledError实例
  • CancelToken:用于创建取消令牌的类
  • VERSION:版本号
  • all:用于发起多个axios并发请求
  • Cancel:CanceledError的别名,用于兼容
  • isAxiosError:用于判断某个对象是否是一个AxiosError(具有isAxiosError:true)
  • spread:Function.prototype.apply的语法糖
  • toFormData:将一个对象转化为FormData
  • AxiosHeaders:用于管理请求头的类
  • HttpStatusCode:HTTP状态码列表
  • formToJSON:将FormData转为Json格式
  • getAdapter:获取有效的适配器的函数
  • mergeConfig:用于合并两个axios配置的方法

默认导出的axios是由内部的createInstance函数调用创建的实例,实例上有一个create方法指向createInstance函数,可以用于创建自定义的axios实例。其余的导出都是实例上的方法。之所以导出一个axios的实例而不是直接导出Axios类作为默认导出,是为了使用的方便。一个原始的使用例子如下:

const axios = new Axios()
axios.request(url, config)
1
2

使用默认导出的axios实例,则可以这样使用

axios.get(url, config)
1

createInstance(defaultConfig)函数的基本逻辑如下:

function createInstance(defaultConfig) {
  const context = new Axios(defaultConfig); // 创建Axios实例
  const instance = bind(Axios.prototype.request, context); // 创建request的绑定函数

  //  将Axios.prototype上的属性拷贝到instance上
  utils.extend(instance, Axios.prototype, context, { allOwnKeys: true });
  // 将Axios实例上的属性拷贝到instance上
  utils.extend(instance, context, null, { allOwnKeys: true });

  // 添加create方法,用于创建新的axios实例
  instance.create = function create(instanceConfig) {
    // 合并配置并创建实例
    return createInstance(mergeConfig(defaultConfig, instanceConfig));
  };

  return instance;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

导出逻辑:

const axios = createInstance(defaults);
axios.Axios = Axios;// 在实力上暴露Axios类
// 接下来在axios上添加其它需要导出的属性和方法
// ...

axios.default = axios;
export default axios
1
2
3
4
5
6
7

可见导出的axios对象实际上Axios.prototype.request方法的绑定方法,该方法还拷贝了Axios.prototype以及Axios实例上的所有属性和方法,同时添加了其它需要导出的类和方法,如AxiosError,CancelToken等。

# axios调用

axios.post('/user', {
  firstName: 'Fred',
  lastName: 'Flintstone'
}, {
  headers: {
    'Content-Type': 'application/json'
  },
  transformRequest: [
    function(data, headers) {}
  ],
  transformResponse: [
    function(data) {}
  ],
  adapter: function(config) {
    return ['xhr']
  },
  //...

})
.then(function (response) {
  console.log(response);
})
.catch(function (error) {
  console.log(error);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

当我们按照类似如上方法调用axios发起一个请求时,axios内部做了以下的事情:

  • 参数统一,Axios.prototype.request既可以接收一个url+config,也可以只接收一个包含url属性的config
  • 合并选项:合并默认的选项以及传入的选项
  • 获取请求方法等属性
  • 解析请求头,生成最终用于创建发起请求的头部信息
  • 初始化一个请求拦截器的链(数组)
  • 设置请求拦截器,将所有请求拦截器逐个压入拦截器链的头部(倒序)
  • 初始化一个响应拦截器的链(数组)
  • 设置响应拦截器,将所有的响应拦截器逐个压入拦截器链的尾部
  • 如果存在异步的请求拦截器
    • 初始化一个请求链,并将dispatchRequest(用于发起请求的方法)压入链
    • 将请求拦截器放到该链的前面,将响应拦截器放到该链的后面,这样就形成了一条:请求拦截器->请求->响应拦截器的链
    • 使用Promise.resolve(config)创建一个初始的promise
    • 遍历整个链,逐个调用promise的then方法,这样就形成了一条完整的promise链式调用
    • 返回最终的promise
  • 如果不存在异步的请求拦截器
    • 创建一个新的配置对象newConfig
    • 遍历请求拦截器,将其结果不断赋值给newConfig
    • 这个过程如果有发生错误则会抛出错误停止执行
    • 使用newConfig这个新的配置调用dispatchRequest函数发起请求,其返回值为一个promise
    • 遍历响应拦截器,调用promise的then方法,形成一条新的promise链
    • 返回promise

# dispatchRequest(config)函数

dispatchRequest函数是整个请求处理链条的核心,也是实际发起请求的地方。dispatchRequest函数接收一个config配置对象,返回一个promise,其调用后执行的过程如下:

  • 检查请求是否已经取消(使用CancelToken或者new AbortController().signal),如果已经取消了,则会抛出中断的错误
  • 实例化请求头为AxiosHeader实例
  • 遍历请求转换器,传入相关配置调用请求转换器,生成转换后的请求数据
  • 规范化请求头
  • 根据配置获取请求适配器,目前支持['xhr', 'http', 'fetch'],也可以自定义适配器
  • 使用获取到的适配器发起请求
  • 请求成功
    • 判断请求是否已经取消了,如果取消了,则会抛出中断错误
    • 调用响应转换器,转换并生成最终响应的数据
    • 响应头转为AxiosHeaders实例
    • 返回响应结果
  • 请求失败,接收失败对象reason
    • 如果reason不是CancelError
      • 判断请求是否已经取消了,如果取消了,则会抛出中断错误
      • 如果有错误中包含reason.response属性(有响应,但是状态码超过了定义的成功范围),则调用响应转换器,转换并生成最终响应的数据,并将响应头转为AxiosHeaders实例
    • 返回一个拒绝的Promise

# 总结

Axios的核心其实是非常简单的,它接收配置后生成一个用于请求和处理请求数据、响应数据的链条,在发起请求成功或者失败后,将结果返回。Axios源码中比较复杂的部分是每一个适配器内部的参数处理,包括参数的序列化、规范化、流的处理等逻辑。尤其是FormData和Stream的处理。这部分的内容比较繁杂,详情查看源码注释。

另外,Axios新增的fetch适配器是有bug的,目前发现了一个处理跨域携带凭证信息时,处理withCredentials配置的bug。在fetch适配器中有如下代码:

// lib/adapters/fetch.js

if (!utils.isString(withCredentials)) {
  withCredentials = withCredentials ? 'cors' : 'omit';
}

request = new Request(url, {
  ...fetchOptions,
  signal: composedSignal,
  method: method.toUpperCase(),
  headers: headers.normalize().toJSON(),
  body: data, 
  duplex: "half",
  withCredentials
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

实际上,Request构造器是不支持withCredentials配置的,正确的配置是credentials,其有效值为omit、same-origin和include,并不包含cors。所以现有的现有适配器实现会导致跨域访问时无法设置携带Cookie。如下代码,withCredentials设置成true后,携带的Cookie仍然会是个空的对象。

axios.get('http://localhost:3000', {
  withCredentials: true,
  adapter: ['fetch']
})
.then(response => console.log(response.data))
  .catch(error => console.error('Error:', error));
1
2
3
4
5
6

修复这个bug只需要将源码修改如下:

if (!utils.isString(withCredentials)) {
  withCredentials = withCredentials ? 'include' : 'omit';
}

request = new Request(url, {
  ...fetchOptions,
  signal: composedSignal,
  method: method.toUpperCase(),
  headers: headers.normalize().toJSON(),
  body: data, 
  duplex: "half",
  credentials: withCredentials
});

1
2
3
4
5
6
7
8
9
10
11
12
13
14

我已经给axios提了issue:Fix fetch request config#6505 (opens new window)

最近更新: 2024/08/13, 17:12
React源码解读

← React源码解读

最近更新
01
基于React16的Webpack升级与构建速度优化
07-09
02
Vue-Router源码解读
07-09
03
Vue2.x源码解读
07-09
更多文章>
Theme by Vdoing | Copyright © 2024-2024 深海鱼 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式