Open pengkobe opened 6 years ago
之前都没有翻译过成篇的文章,这次借阮博士的分享的这篇文章,小小的实践一下,所以难免有很多疏漏,得好好加油,继续努力。
原文地址: How to Implement an HTTP Request Library with Axios
在前端开发过程中,我们经常会遇到发送异步请求的场景,而使用 HTTP 库可以很大程度上减少我们开发成本以及提高开发效率。 Axios 近年来普遍使用的 HTTP 库,如今,在 github 上有奖金 4 万个 star,很多大牛也在极力推广使用这个库。 所以,我们有必要了解 Axios 是如何设计的,当作者发布这篇文章的时候,Axios 的最新版本是 0.18.0 ,文章里的代码示例和分析也会基于这个版本,它的源代码位于 lib/ 目录下,文中代码的引用路径也是基于这个文件夹。 这篇文章将涵盖以下内容
0.18.0
lib/
为了理解 Axios 的设计,我们首先得学会怎么使用,以下是几个简单的使用示例
axios({ method:'get', url:'http://bit.ly/2mTM3nY', responseType:'stream' }) .then(function(response) { response.data.pipe(fs.createWriteStream('ada_lovelace.jpg')) });
这个是官方的示例代码,我们可以看到,其使用起来跟 jQuery ajax 有很大的相似之处,都返回了一个 Promise 对象来进行处理( 其也可以使用成功回调,不过更建议使用 Promise 和 await )
Promise
这个示例很简单,这里也不多说,接下来,我们来看看如何添加一个拦截函数
// 客户端请求拦截器 axios.interceptors.request.use(function (config) { // 发起请求前 return config; }, function (error) { // 请求错误处理 return Promise.reject(error); }); // 服务端返回拦截器 axios.interceptors.response.use(function (response) { // 处理返回数据 return response; }, function (error) { // 返回错误处理 return Promise.reject(error); })
从上述代码,我们可以知道,我们可以在请求正式发送前修改配置参数,也可以在请求返回后对数据执行特定的操作,当然,我们也可以对错误进行处理。
当我们开发查询相关的模块时,我们经常需要频繁的发起请求,通常情况下,我们需要在发起新的请求前取消掉上一次发起的请求,这无疑是 axios 的一个优势,axios 取消请求的代码示例如下
const CancelToken = axios.CancelToken; const source = CancelToken.source(); axios.get('/user/12345', { cancelToken: source.token }).catch(function(thrown) { if (axios.isCancel(thrown)) { console.log('Request canceled', thrown.message); } else { // handle error } }); axios.post('/user/12345', { name: 'new name' }, { cancelToken: source.token }) // cancel the request (the message parameter is optional) source.cancel('Operation canceled by the user.');
从上诉代码我们可以看到有一个叫作 CancelToken 的变量,借助其可以实现请求取消,不过这种方式已经被官方舍弃了,具体原因我们稍后会提到。
CancelToken
通过上述案例分析,大家已经清楚如何使用 axios 了,接下来,我们会分析 axios 核心模块的设计与实现方式,下述图片涵盖了这篇文章中有分析到的模块,如果你有兴趣,也可以直接从 github 上下载 axios 源码研究,能够让你对相关模块有更深的了解。
request 模块位于 core/dispatchReqeust.js文件. 这里我选取了相关片段做一个简要介绍
core/dispatchReqeust.js
module.exports = function dispatchRequest(config) { throwIfCancellationRequested(config); // 其它代码(略) // 这个适配器能够自动根据环境选择发起请求的方式,选择 Node 或 XHR var adapter = config.adapter || defaults.adapter; return adapter(config).then(function onAdapterResolution(response) { throwIfCancellationRequested(config); // 其它代码(略) return response; }, function onAdapterRejection(reason) { if (!isCancel(reason)) { throwIfCancellationRequested(config); // 其它代码(略) return Promise.reject(reason); }); };
从上述代码,我们可以知道 dispatchRequest 方法是通过 config.adapter 来实现请求发送的,我们也可以通过传一个符合规范的适配器方法替换原生的模块( 当然,我们并不是真的这么做,只是说这种解耦方式还是不错的 )
dispatchRequest
config.adapter
在 default.js 相关文件中, 我们可以看到适配器的选择逻辑,其是根据一些特殊变量以及当前容器的一些构造函数来进行判断的。
function getDefaultAdapter() { var adapter; if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') { // Node 端 adapter = require('./adapters/http'); } else if (typeof XMLHttpRequest !== 'undefined') { // 浏览器端 adapter = require('./adapters/xhr'); } return adapter; }
现在,让我们来看看 axios 是如何实现请求和返回的拦截器方法的,先让我们看看公布的相关接口,以 request 函数为例
request
Axios.prototype.request = function request(config) { // 其它代码 var chain = [dispatchRequest, undefined]; var promise = Promise.resolve(config); this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { chain.unshift(interceptor.fulfilled, interceptor.rejected); }); this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { chain.push(interceptor.fulfilled, interceptor.rejected); }); while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); } return promise; };
这个函数是 axios 发送请求的接口,因为函数的实现比较长,这里我只做简要的说明 chain 是执行队列,初始值是带有配置参数的 Promise ,在chain 队列里,第一个值是 dispatchReqeust,其用来发送请求,第二值是 undefined,相对于 dispatchReqeust,那么 undefined 的作用是什么呢?其主要是为了作为 Promise 中成功和失败回调函数的默认值,从代码 promise = promise.then(chain.shift(), chain.shift());中我们也可以看到,因此 dispatchReqeust 和 undefined 可以看做是一对函数。
promise = promise.then(chain.shift(), chain.shift());
在chain 队列里,dispatchReqeust 函数用于发送请求,在此之前,使用 unshift 插入了一个请求拦截器,在此之后,使用 push 在后方插入了一个,需要注意的是,这两个拦截器一般是成对出现的。 从上述代码,我们粗略的知道了拦截器的使用,接下来,我们学习下如何取消请求。
unshift
请求取消模块代码位于 Cancel/文件夹, 让我们看看其中的关键代码。 首先,让我们瞧一瞧 Cancel这个类,这个类主要用于记录取消状态,具体代码如下
Cancel/
Cancel
function Cancel(message) { this.message = message; } Cancel.prototype.toString = function toString() { return 'Cancel' + (this.message ? ': ' + this.message : ''); }; Cancel.prototype.__CANCEL__ = true;
在 CancelToken 中,其通过传递一个 Promise 对象实现了 HTTP 请求取消 ,具体代码如下
function CancelToken(executor) { if (typeof executor !== 'function') { throw new TypeError('executor must be a function.'); } var resolvePromise; this.promise = new Promise(function promiseExecutor(resolve) { resolvePromise = resolve; }); var token = this; executor(function cancel(message) { if (token.reason) { // cancle 方法已经调用了 return; } token.reason = new Cancel(message); resolvePromise(token.reason); }); } CancelToken.source = function source() { var cancel; var token = new CancelToken(function executor(c) { cancel = c; }); return { token: token, cancel: cancel }; };
相应的 cancel-request 代码位于 adapter/xhr.js 文件中
adapter/xhr.js
if (config.cancelToken) { // 等待 cancel 方法调用 config.cancelToken.promise.then(function onCanceled(cancel) { if (!request) { return; } request.abort(); reject(cancel); // 重置请求 request = null; }); }
通过对以上代码的分析,我们简要总结下取消请求代码的实现逻辑
cancle
request.abort()
如前面所说,axios 并没有特殊对待请求发送方法 dispatchRequest,而是把其放到了队列的中间来保证处理的一致性,同时也提高了代码的可读性。
在适配器处理逻辑中,http 和 xhr 模块并没有嵌入到 dispatchRequest 中,而是在 default.js 文件中通过配置方法进行引入,这不仅让模块很好的进行解耦,也使得未来用户自定义自己的适配器成为可能。
在取消请求逻辑中,axios 使用 Promise 作为触发器,传递 resolve 函数到回调函数中作为外部参数,这不仅保证了内部逻辑的一致性,也保证了在取消发送请求时,不需要直接改变相关类的数据,从而很大程度上避免了侵入其它模块。
这篇文章详细介绍了 axios 的使用、设计思想以及实现方法,阅读后,你能够知道 axios 是如何设计的以及了解到模块之间的封装与交互。 当然,这篇文章也仅仅是介绍了核心模块的相关的内容,如果你对其它代码有兴趣,可以直接去 github阅读相关代码。
有任何问题和建议,欢迎留言。
原文地址: How to Implement an HTTP Request Library with Axios
背景
在前端开发过程中,我们经常会遇到发送异步请求的场景,而使用 HTTP 库可以很大程度上减少我们开发成本以及提高开发效率。
Axios 近年来普遍使用的 HTTP 库,如今,在 github 上有奖金 4 万个 star,很多大牛也在极力推广使用这个库。 所以,我们有必要了解 Axios 是如何设计的,当作者发布这篇文章的时候,Axios 的最新版本是
0.18.0
,文章里的代码示例和分析也会基于这个版本,它的源代码位于lib/
目录下,文中代码的引用路径也是基于这个文件夹。这篇文章将涵盖以下内容
如何使用 Axios
为了理解 Axios 的设计,我们首先得学会怎么使用,以下是几个简单的使用示例
发送请求
这个是官方的示例代码,我们可以看到,其使用起来跟 jQuery ajax 有很大的相似之处,都返回了一个
Promise
对象来进行处理( 其也可以使用成功回调,不过更建议使用 Promise 和 await )这个示例很简单,这里也不多说,接下来,我们来看看如何添加一个拦截函数
添加拦截器
从上述代码,我们可以知道,我们可以在请求正式发送前修改配置参数,也可以在请求返回后对数据执行特定的操作,当然,我们也可以对错误进行处理。
取消 HTTP 请求
当我们开发查询相关的模块时,我们经常需要频繁的发起请求,通常情况下,我们需要在发起新的请求前取消掉上一次发起的请求,这无疑是 axios 的一个优势,axios 取消请求的代码示例如下
从上诉代码我们可以看到有一个叫作
CancelToken
的变量,借助其可以实现请求取消,不过这种方式已经被官方舍弃了,具体原因我们稍后会提到。axios 核心模块时如何设计实现的?
通过上述案例分析,大家已经清楚如何使用 axios 了,接下来,我们会分析 axios 核心模块的设计与实现方式,下述图片涵盖了这篇文章中有分析到的模块,如果你有兴趣,也可以直接从 github 上下载 axios 源码研究,能够让你对相关模块有更深的了解。![](https://www.tutorialdocs.com/upload/2018/08/axios-module.png)
HTTP request 模块
request 模块位于
core/dispatchReqeust.js
文件. 这里我选取了相关片段做一个简要介绍从上述代码,我们可以知道
dispatchRequest
方法是通过config.adapter
来实现请求发送的,我们也可以通过传一个符合规范的适配器方法替换原生的模块( 当然,我们并不是真的这么做,只是说这种解耦方式还是不错的 )在 default.js 相关文件中, 我们可以看到适配器的选择逻辑,其是根据一些特殊变量以及当前容器的一些构造函数来进行判断的。
Interceptor 模块
现在,让我们来看看 axios 是如何实现请求和返回的拦截器方法的,先让我们看看公布的相关接口,以
request
函数为例这个函数是 axios 发送请求的接口,因为函数的实现比较长,这里我只做简要的说明
chain 是执行队列,初始值是带有配置参数的 Promise ,在chain 队列里,第一个值是 dispatchReqeust,其用来发送请求,第二值是 undefined,相对于 dispatchReqeust,那么 undefined 的作用是什么呢?其主要是为了作为 Promise 中成功和失败回调函数的默认值,从代码
promise = promise.then(chain.shift(), chain.shift());
中我们也可以看到,因此 dispatchReqeust 和 undefined 可以看做是一对函数。在chain 队列里,dispatchReqeust 函数用于发送请求,在此之前,使用
unshift
插入了一个请求拦截器,在此之后,使用 push 在后方插入了一个,需要注意的是,这两个拦截器一般是成对出现的。 从上述代码,我们粗略的知道了拦截器的使用,接下来,我们学习下如何取消请求。Cancel-request 模块
请求取消模块代码位于
Cancel/
文件夹, 让我们看看其中的关键代码。 首先,让我们瞧一瞧Cancel
这个类,这个类主要用于记录取消状态,具体代码如下在 CancelToken 中,其通过传递一个 Promise 对象实现了 HTTP 请求取消 ,具体代码如下
相应的 cancel-request 代码位于
adapter/xhr.js
文件中通过对以上代码的分析,我们简要总结下取消请求代码的实现逻辑
CancelToken
类的实例以及调用了cancle
方法进行初始化。 当 source 方法返回实例 A 时,一个处于 pending 状态的 promise 也被创建了,当把实例 A 传入 axis 后,这个 promise 就被用来做取消请求的触发器了。 当 source 方法返回的 cancel 方法被调用时,这个 promise 的状态就会由 pending 转为 fulfilled,then 回调函数也会立即执行,至此,执行取消逻辑的脚本request.abort()
也会被触发axios 的优势是什么
发送请求
如前面所说,axios 并没有特殊对待请求发送方法
dispatchRequest
,而是把其放到了队列的中间来保证处理的一致性,同时也提高了代码的可读性。适配器
在适配器处理逻辑中,http 和 xhr 模块并没有嵌入到 dispatchRequest 中,而是在 default.js 文件中通过配置方法进行引入,这不仅让模块很好的进行解耦,也使得未来用户自定义自己的适配器成为可能。
取消请求
在取消请求逻辑中,axios 使用 Promise 作为触发器,传递 resolve 函数到回调函数中作为外部参数,这不仅保证了内部逻辑的一致性,也保证了在取消发送请求时,不需要直接改变相关类的数据,从而很大程度上避免了侵入其它模块。
总结
这篇文章详细介绍了 axios 的使用、设计思想以及实现方法,阅读后,你能够知道 axios 是如何设计的以及了解到模块之间的封装与交互。
当然,这篇文章也仅仅是介绍了核心模块的相关的内容,如果你对其它代码有兴趣,可以直接去 github阅读相关代码。
有任何问题和建议,欢迎留言。