内容简介:在前端开发过程中,我们经常遇到需要用到异步请求的场景。所以,一个功能齐全的 HTTP 请求库,可以极大的减少开发时间,提高开发效率。axios 是近几年非常热门 HTTP 请求库。目前在因此,有必要去了解一下 axios 是如何设计的、是如何实现 HTTP 请求库的。写这篇文章时 axios 的版本是 0.18.0,我们以此版本为例,来解读分析源码细节。axios 的源码是在 lib 目录下,以下涉及到的路径都是相对
在前端开发过程中,我们经常遇到需要用到异步请求的场景。所以,一个功能齐全的 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。
发送请求
axios({ method:'get', url:'http://bit.ly/2mTM3nY', responseType:'stream' }) .then(function(response) { response.data.pipe(fs.createWriteStream('ada_lovelace.jpg')) });
这是官方提供的 API 例子。从例子可以看出来,axios 的使用方式和 jQuery 的 ajax 非常相似,它们都返回 Promise (此例中 axios 也可以使用成功回调的方式,但是推荐使用 Promise 或者 await)来进行后续操作。
这个例子很简单,不需要过多解释,我们来看怎么添加 过滤函数(filter function) 。
添加拦截函数
// 添加一个请求拦截器 // 注意这里有两个方法 —— 一个成功时的和一个失败时的,后面会对这些做解释 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); });
从上述代码可知:在请求前,可以对请求 config
的参数做处理;请求后响应时,也可以对返回的数据做些特殊处理。同时,在请求或者响应失败时,我们也可以做相应的特殊错误处理。
取消 HTTP 请求
在开发搜索相关的模块时,我们经常需要频繁发起数据搜索的请求。一般来说,在发起下一次请求时,我们要取消上一次的请求。同时也建议取消和请求相关联的函数调用。
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.');
从上述例子可知,axios 使用基于 CancelToken 的撤销方式的提案。但是该提案已经被撤销了, 查看详情 。撤销的实现细节在后面的源码分析部分会解释。
axios 核心模块的设计和实现方式
通过上面的例子,相信大家对 axios 的使用有了一个大致的了解。下面,我们根据 axios 的模块来分析其设计和实现。下图展示了该博文涉及到的 axios 相关目录。如果你感兴趣的话,建议 clone 相关的代码来看,这样可以对相应的模块有更深的理解。
HTTP 请求模块(HTTP request module)
和请求模块相关的代码在 core/dispatchReqeust.js
文件中。这里我选取了部分关键源码来做简单的介绍:
// core/dispatchReqeust.js module.exports = function dispatchRequest(config) { throwIfCancellationRequested(config); // other source code // 默认的适配器模块是根据当前环境来选择使用 Node 或者 XHR 来发起请求 var adapter = config.adapter || defaults.adapter; return adapter(config).then(function onAdapterResolution(response) { throwIfCancellationRequested(config); // other source code return response; }, function onAdapterRejection(reason) { if (!isCancel(reason)) { throwIfCancellationRequested(config); // other source code return Promise.reject(reason); }); };
通过上面的代码可知, dispatchRequest
方法是用来获取发送请求模块,发送请求模块是通过 config.adapter
得到。我们也可以通过传入符合规范的适配器函数(adapter function)来代替原始的模块(我们一般不会这么做,但是这是一个低耦合扩展点)。
在 default.js
文件里,可以看到 adapter
生成的逻辑:通过一些特殊的属性和当前执行环境的构造函数来判断。
// default.js function getDefaultAdapter() { var adapter; // Only Node.js has the classes of which variable type is process. if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') { // Node.js request module. adapter = require('./adapters/http'); } else if (typeof XMLHttpRequest !== 'undefined') { // The browser request module. adapter = require('./adapters/xhr'); } return adapter; }
axios 里的 XHR 模块是对 XMLHTTPRequest
对象的封装,相对比较简单。所以这里不会过多的说明,有兴趣的话可以自己去阅读,代码在 adapters/xhr.js
文件。
拦截器模块(Interceptor module)
现在来看看 axios 是如何实现请求和响应的拦截器方法。先来看看 axios 的统一接口 —— request
函数。
// core/Axios.js Axios.prototype.request = function request(config) { // other code 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 是执行队列。队列的初始值是一个带 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
。这个类是用来标记取消状态。代码细节如下:
// cancel/Cancel.js function Cancel(message) { this.message = message; } Cancel.prototype.toString = function toString() { return 'Cancel' + (this.message ? ': ' + this.message : ''); }; Cancel.prototype.__CANCEL__ = true;
在 CancelToken
类中,通过传递 Promise 方法来实现 HTTP 请求的取消,具体代码如下:
// cancel/CancelToken.js 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) { // Cancellation has already been requested 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 }; };
相关的取消请求的代码在 adapter/xhr.js
文件:
// adapter/xhr.js if (config.cancelToken) { // Wait for the cancellation. config.cancelToken.promise.then(function onCanceled(cancel) { if (!request) { return; } request.abort(); reject(cancel); // Reset the request. request = null; }); }
通过上述取消 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 内部设计进行分析,从整体上了解其中的核心设计思想。翻译可能有不准确或者错误地方,欢迎指正!
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 【斯坦福算法分析和设计02】渐进分析
- Spring 框架的设计理念与设计模式分析
- thrift源码分析-架构设计
- Flink 编程 API 设计分析
- Redis(十一):哨兵模式架构设计分析
- PHP设计模式之注册树模式分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Two Scoops of Django
Daniel Greenfeld、Audrey M. Roy / CreateSpace Independent Publishing Platform / 2013-4-16 / USD 29.95
Two Scoops of Django: Best Practices For Django 1.5 is chock-full of material that will help you with your Django projects. We'll introduce you to various tips, tricks, patterns, code snippets, and......一起来看看 《Two Scoops of Django》 这本书的介绍吧!