原文: How to Implement an HTTP Request Library with Axios
作者:Alex
概述
在前端开发过程中,我们经常遇到需要用到异步请求的场景。所以,一个功能齐全的 HTTP 请求库,可以极大的减少开发时间,提高开发效率。
axios 是近几年非常热门 HTTP 请求库。目前在 Github 已经有超过 40k 的 stars,也得到很多权威人士的推荐。
因此,有必要去了解一下 axios 是如何设计的、是如何实现 HTTP 请求库的。写这篇文章时 axios 的版本是 0.18.0,我们以此版本为例,来解读分析源码细节。axios 的源码是在 lib 目录下,以下涉及到的路径都是相对 lib
目录。
此篇主要讨论一下几点:
- 如何使用 axios
- axios 核心模块(请求 requests, 拦截器 interceptors, 撤销 withdrawals)的设计和实现方式
- axios 的设计优点
如何使用 axios
在理解 axios 的设计之前,我们先了解一下如何使用 axios。我们通过一个简单的例子来说明下面的 axios API。
发送请求
1 | axios({ |
这是官方提供的 API 例子。从例子可以看出来,axios 的使用方式和 jQuery 的 ajax 非常相似,它们都返回 Promise (此例中 axios 也可以使用成功回调的方式,但是推荐使用 Promise 或者 await)来进行后续操作。
这个例子很简单,不需要过多解释,我们来看怎么添加 过滤函数(filter function)。
添加拦截函数
1 | // 添加一个请求拦截器 |
从上述代码可知:在请求前,可以对请求 config
的参数做处理;请求后响应时,也可以对返回的数据做些特殊处理。同时,在请求或者响应失败时,我们也可以做相应的特殊错误处理。
取消 HTTP 请求
在开发搜索相关的模块时,我们经常需要频繁发起数据搜索的请求。一般来说,在发起下一次请求时,我们要取消上一次的请求。同时也建议取消和请求相关联的函数调用。
1 | const CancelToken = axios.CancelToken; |
从上述例子可知,axios 使用基于 CancelToken 的撤销方式的提案。但是该提案已经被撤销了,查看详情。撤销的实现细节在后面的源码分析部分会解释。
axios 核心模块的设计和实现方式
通过上面的例子,相信大家对 axios 的使用有了一个大致的了解。下面,我们根据 axios 的模块来分析其设计和实现。下图展示了该博文涉及到的 axios 相关目录。如果你感兴趣的话,建议 clone 相关的代码来看,这样可以对相应的模块有更深的理解。
HTTP 请求模块(HTTP request module)
和请求模块相关的代码在 core/dispatchReqeust.js
文件中。这里我选取了部分关键源码来做简单的介绍:
1 | // core/dispatchReqeust.js |
通过上面的代码可知,dispatchRequest
方法是用来获取发送请求模块,发送请求模块是通过 config.adapter
得到。我们也可以通过传入符合规范的适配器函数(adapter function)来代替原始的模块(我们一般不会这么做,但是这是一个低耦合扩展点)。
在 default.js
文件里,可以看到 adapter
生成的逻辑:通过一些特殊的属性和当前执行环境的构造函数来判断。
1 | // default.js |
axios 里的 XHR 模块是对 XMLHTTPRequest
对象的封装,相对比较简单。所以这里不会过多的说明,有兴趣的话可以自己去阅读,代码在 adapters/xhr.js
文件。
拦截器模块(Interceptor module)
现在来看看 axios 是如何实现请求和响应的拦截器方法。先来看看 axios 的统一接口 —— request
函数。
1 | // core/Axios.js |
这个函数是 axios 发送请求的接口。因为这个功能实现相对较长,所以我简要介绍一下相关的设计思路:
- chain 是执行队列。队列的初始值是一个带 config 参数的 Promise。
- chain 执行队列中,插入了两个值,一个是用来发送请求的初始化函数
dispatchRequest
,一个是
和dispatchRequest
配对的函数undefined
。为什么要加undefined
呢?因为在 Promise 里需要一个成功回调和一个失败回调,从代码段promise = promise.then(chain.shift(), chain.shift());
也可以看出来。所以,dispatchRequest
和undefined
可以当成是成对的函数。 chain
执行队列中,发送请求的dispatchRequest
函数位于“中间位置”。在它的前面是请求拦截器,通过unshift
方法插入;在dispatchRequest
后面的是响应拦截器,通过push
方法插入。需要注意的是这些方法都是成对添加的,也就意味着一次会添加两个方法。
通过上述 request
的代码,我们大致知道怎么使用拦截器了。现在我们来看看怎么取消 HTTP 请求。
取消请求模块(Cancel-request module)
和取消相关的模块在 Cancel/
目录。现在来看一下相关的核心代码。
首先来看一下元类 Cancel
。这个类是用来标记取消状态。代码细节如下:
1 | // cancel/Cancel.js |
在 CancelToken
类中,通过传递 Promise 方法来实现 HTTP 请求的取消,具体代码如下:
1 | // cancel/CancelToken.js |
相关的取消请求的代码在 adapter/xhr.js
文件:
1 | // adapter/xhr.js |
通过上述取消 HTTP 请求的代码,我们简要的解释一下相关实现逻辑:
- 在需要取消的请求中,调用
source
方法来初始化,该方法会返回CancelToken
类的实例 A 和cancle
方法。 - 当
source
方法返回实例 A 时,会初始化一个处于pending
状态的 promise。然后把实例 A 传递给 axios,promise 就可以当做取消请求的触发器。 - 当调用
source
方法返回的cancel
方法时,实例 A 中的 promise 从 pending 转化成 fulfilled 状态,然后马上触发回调函数。从而 axios 的取消逻辑 ——request.abort()
被触发。
axios 的设计优点
发送请求函数的处理逻辑
正如前面章节提到的,axios 并没有把发送请求的 dispatchRequest
函数当成特殊函数对待。实际上,dispatchRequest
函数放在队列的中间,从而保证队列处理的一致性并提高代码的可读性。
适配器的处理逻辑
在适配器的的处理逻辑中,http
和 xhr
模块(http
用于 Node.js 发送请求,xhr
用于浏览器发送请求)并不直接放在 dispatchRequest
模块中,而是通过默认配置从 default.js
引入。从而不仅可以保证两个模块的低耦合,而且为将来的用户预留了定制化请求的空间。
取消 HTTP 请求的处理逻辑
在取消 HTTP 请求的逻辑中,axios 设计使用 Promise 作为触发器,把 resolve
方法作为 callback
的参数传递到外面。这样不仅确保内部逻辑的一致性,还可以确保在需要取消请求时,不必直接修改相关类的样本数据,从而可以最大程度避免侵入其他的模块。
总结
本文详细介绍了 axios 的使用,设计思路和实现方法。阅读后,你可以知道 axios 的设计,同时学习模块的封装和交互。
本文只介绍了 axios 的核心模块。如果你对其他源码感兴趣,你可以去 Github 上查看。
在 阮一峰每周分享第 20 期 看到这篇文章。文章没有很复杂的东西,也不是很细致的使用说明或者源码解读,作者主要是对 axios 内部设计进行分析,从整体上了解其中的核心设计思想。翻译可能有不准确或者错误地方,欢迎指正!
- 本文链接: https://blog.hhking.cn/2018/09/04/http-request-library-with-axios/
- 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!