Axios 源码解读 —— request 篇

Axios 是一个基于 promise 网络请求库,作用于 node.js 和浏览器中。 它是 isomorphic 的(即同一套代码可以运行在浏览器和 node.js 中)。在服务端它使用原生 node.js http 模块, 而在客户端 (浏览端) 则使用 XMLHttpRequests

从 Axios 的官方介绍可以得知,这是一个可以同时运行在浏览器客户端 + Node 服务端的网络请求库,在浏览器运行时,使用 XMLHttpRequests 构建请求,在 Node 环境时使用 nodehttp 模块构建网络请求。

今天,我们围绕着 axios 的源码实现进行解读,解读完成后,再实现一个简易的 axios 库。

我们先来看看 axios 库的项目目录结构。(如下图)

image

从上图可以得到两个信息:

  1. axios 的核心文件是 lib/axios,所以我们如果只关注 axios 运行时的话,只需要看 lib 这个目录下的文件即可。
  2. axios 运行只依赖一个第三方库 follow-redirects,这个库是用于处理 HTTP 重定向请求的,axios 的默认行为是跟随重定向的,可以猜测是用这个库来做重定向跟随的。 —— 如果你不想要自动跟随重定向,需要显式声明 maxRedirects=0

我在百度一直没找到 axios 的官方文档,所以这里贴一份 axios 官方文档,大家可以参考使用。

lib/axios

我们打开 lib/axios.js 文件看看。(如下图)

image

重点关注这几行核心就可以了。

行数 描述
26 文档中的 axios.create 调用的是 createInstance 函数,这个函数将会新建一个 Axios 实例
34 新建了一个默认的 axios 实例
37 ~ 52 默认的 axios 实例添加了大量的属性和方法
57 将默认的 axios 实例导出

Axios

接下来,我们来看看 Axios 类,这是 axios 源码最核心的部分,位于 lib/core/Axios.js

function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

Axios 接收配置,将 instanceConfig 配置存在 axios.defaults 属性中,用于后续的请求。

同时,通过 InterceptorManager 来管理请求拦截器和响应拦截器,在后面我们会展开聊聊这个拦截管理器 —— InterceptorManager

axios 默认的实例将会使用 lib/defaults.js 中的配置进行创建。

Axios 这个设置,我们就知道文档中关于修改配置的这部分内容缘由了。(如下图)

image

我们也能看出 Axios 实例是 axios 对于 网络请求-默认配置 的最小单位,而不存在所有实例共享的一套 “全局默认配置”

但是我们可以通过两个方法来实现。

其中一个方法就是我们写两套配置,一套全局默认配置,一套各实例的个性化配置,在创建 axios 实例的时候做手动合并。

当然,还有个更聪明的方法,我们先来看看之前 P1 的 createInstance 方法。

function createInstance(defaultConfig) {
  var context = new Axios(defaultConfig);
  var instance = bind(Axios.prototype.request, context);
  
  //...

  instance.create = function create(instanceConfig) {
    // 这里通过闭包继承了 defaultConfig 配置,新创建的实例会继承原实例的配置
    return createInstance(mergeConfig(defaultConfig, instanceConfig));
  };

  return instance;
}

从代码中可以看出,createInstance 方法内部通过闭包继承了 defaultConfig 配置,新创建的实例会继承原实例的配置。

这样的话,我们还可以通过先用一套全局默认配置创建一个 axios 实例,然后再使用这个 axios 实例,调用 axios/instance.create 方法创建其他的 axios 实例,这样所有的 axios 实例都可以继承全局默认配置。

拦截管理器 —— InterceptorManager

我们现在来看看 axios 内部的拦截管理器 InterceptorManager。(如下图)

image

InterceptorManager 的总体实现还是挺简单的,内部有个 handlers 数组,存放了所有通过 use 方法注册的拦截器。

use 方法返回了 handlers.length - 1 作为拦截器 id,在调用 eject 方法时,会将对应 ID 下标的拦截器设置为 null

forEach 方法对所有的拦截器方法进行遍历,执行传入的 fn 回调函数,将拦截器作为参数传入 fn 中。

从这里可以看出,axios 内部的职责划分还是比较清晰的,InterceptorManager 只负责收集管理拦截器,而不关心拦截器的执行逻辑。

不过我感觉 forEach 方法设计的有些冗余,如果是我来设计的话,我可能只会暴露一个 getter 方法让外部获取 handlers。这里作者的设计可能有一些别的考虑,我暂时还没有想到。

request

接下来,我们看看 Axios 类的核心方法,也就是 request 方法,axios 通过 request 方法来发起真实网络请求。

这一段代码比较长,我会对这一大段代码进行逐行解析。request 方法中对于拦截器和请求的处理非常优雅,我会重点介绍。

相对比较简单的部分我直接用表格介绍(如下)

image
行数 描述
32 ~ 41 判断首个参数,组装 config 配置,禁止无 url 的请求
46 ~ 52 设置请求方法,如果未声明则使用默认配置的请求方法,如果未设置默认的请求方法,则使用 get 请求

下面我们要着重介绍一下 拦截器 的处理,我们先看 请求拦截器

image

这里有两个文档中未说明的参数,这里我们做一下解释:

  • 58 行:使用 use 注册拦截器时,第三个参数中的 options.runWhen 方法将会先被调用,如果该方法返回 false,则跳过该请求拦截器。
  • 62 行:使用 use 注册拦截器时,第三个参数中的 options.synchronous 参数将显式声明该拦截器为 同步,否则默认为 异步 拦截器,将会通过 promise 进行调用。 —— 其实我觉得这个参数没什么意义了,统一 异步 就好了。可能作者还考虑了其他的某些同步场景,我暂时还没有想到。

重点注意:第 64 行使用了 unshift 方法将 请求拦截器 按注册的逆序添加到 requestInterceptorChain 中,供后续执行。

这也就意味着 请求拦截器 对同一份配置的修改,后面加的拦截器是无法覆盖前置拦截器的。

我们看看下面这个请求拦截器案例就知道了。

image

后设置的拦截器看起来并未生效,看过源码我们就知道了,其实是执行顺序导致的。

axios 这么设计的原因可能是防止 请求拦截器 滥用导致配置被后续处理人覆盖。—— 但这点没有在文档说明,如果正好碰上这种场景,难免会造成一些困惑。

响应拦截器 的处理就简单多了,相信我应该不用多做解释了。(如下图)

image

稍微值得注意的是,拦截器 将成功处理和错误处理都添加到了内部拦截器数组中,也就是说数组内部是这样的:

['拦截器成功处理处理函数', '拦截器出错处理函数', '拦截器成功处理处理函数', '拦截器出错处理函数', ...]

了解这个数据结构,对最后这一段核心代码的实现理解是有帮助的(如下图)

image

我们需要对每一行代码进行逐行解析:

行数 描述
74 判断是否为异步请求拦截器(默认:是)
75 声明 chain 数组,数组第一个元素是发起请求的方法(可以简单理解为 fetch 方法),第二个元素是为了 82 行凑数的 undefined
77 将所有的 请求拦截器 添加到 chain 的开头位置
78 将所有的 响应拦截器 添加到 chain 的尾部位置
80 ~ 83 config 构建第一个 Promise,然后按顺序依次执行 chain —— 请求拦截器 -> 真实请求 -> 响应拦截器,每次执行传入的就是 成功处理函数(作为 resolve) 和 失败处理函数(作为 reject

最后一段 chain 的执行,非常优雅的阐述了 axios 内部的工作流程,也就是 请求拦截器 -> 真实请求 -> 响应拦截器 这一套核心工作流。

建议大家可以仔细再看看最后一段函数的处理,仔细品一品。

下面还有一段关于 同步请求拦截器 的处理,基本上是大同小异的,感兴趣的童鞋可以自行阅读一下。

小结

好了,到这里,axios 的基本结构和核心工作流程就解析完了。

下一章,我会针对 真实请求 —— dispatchRequest 进行详细解析,请大家继续关注。

最后一件事

如果您已经看到这里了,希望您还是点个赞再走吧~

您的点赞是对作者的最大鼓励,也可以让更多人看到本篇文章!

如果觉得本文对您有帮助,请帮忙在 github 上点亮 star 鼓励一下吧!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容