在前端开发过程当中,咱们常常会遇到须要发送异步请求的状况。而使用一个功能齐全,接口完善的HTTP请求库,可以在很大程度上减小咱们的开发成本,提升咱们的开发效率。javascript
axios是一个在近些年来很是火的一个HTTP请求库,目前在GitHub中已经拥有了超过40K的star,受到了各位大佬的推荐。前端
今天,咱们就来看下,axios究竟是如何设计的,其中又有哪些值得咱们学习的地方。我在写这边文章时,axios的版本为0.18.0。咱们就以这个版本的代码为例,来进行具体的源码阅读和分析。当前axios全部源码文件都在lib
文件夹中,所以咱们下文中提到的路径均是指lib
文件夹中的路径。java
本文的主要内容有:ios
想要了解axios的设计,咱们首先须要来看下axios是如何使用的。咱们经过一个简单示例来介绍如下axios的API。git
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(也能够经过success的callback,不过建议使用Promise或者await)来继续后面的操做。github
这个代码示例很简单,我就不过多赘述了,下面让咱们来看下如何添加一个过滤器函数。ajax
// 增长一个请求拦截器,注意是2个函数,一个处理成功,一个处理失败,后面会说明这种状况的缘由 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参数进行数据处理;而在请求响应后,咱们也能针对返回的数据进行特定的操做。同时,在请求失败和响应失败时,咱们均可以进行特定的错误处理。axios
在完成搜索相关的功能时,咱们常常会须要频繁的发送请求来进行数据查询的状况。一般来讲,咱们在下一次请求发送时,就须要取消上一次请求。所以,取消请求相关的功能也是一个优势。axios取消请求的示例代码以下:promise
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的文件,若是读者有兴趣的话,能够经过clone相关代码结合博客进行阅读,这样可以加深对相关模块的理解。
做为核心模块,axios发送请求相关的代码位于core/dispatchReqeust.js
文件中。因为篇幅有限,下面我选取部分重点的源码进行简单的介绍:
module.exports = function dispatchRequest(config) { throwIfCancellationRequested(config); // 其余源码 // default adapter是一个能够判断当前环境来选择使用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
来获得发送请求的模块的,咱们本身也能够经过传入符合规范的adapter函数来替换掉原生的模块(虽然通常不会这么作,不过也算是一个松耦合扩展点)。
在default.js
文件中,咱们可以看到相关的adapter选择逻辑,即根据当前容器中特有的一些属性和构造函数来进行判断。
function getDefaultAdapter() { var adapter; // 只有Node.js才有变量类型为process的类 if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') { // Node.js请求模块 adapter = require('./adapters/http'); } else if (typeof XMLHttpRequest !== 'undefined') { // 浏览器请求模块 adapter = require('./adapters/xhr'); } return adapter; }
axios中XHR模块较为简单,为XMLHTTPRequest对象的封装,咱们在这里就不过多进行介绍了,有兴趣的同窗能够自行阅读,代码位于adapters/xhr.js
文件中。
了解了dispatchRequest
实现的HTTP请求发送模块,咱们来看下axios是如何处理请求和响应拦截函数的。让咱们看下axios中请求的统一入口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发送请求的入口,由于函数实现比较长,我就简单说一下相关的设计思路:
dispatchReqeust
和与之对应的undefined
。后面须要增长一个undefined
是由于在Promise中,须要一个success和一个fail的回调函数,这个从代码promise = promise.then(chain.shift(), chain.shift());
就可以看出来。所以,dispatchReqeust
和undefined
咱们能够成为一对函数。dispatchReqeust
是处于中间的位置。它的前面是请求拦截器,经过unshift
方法放入;它的后面是响应拦截器,经过push
放入。要注意的是,这些函数都是成对的放入,也就是一次放入两个。经过上面的request
代码,咱们大体知道了拦截器的使用方法。接下来,咱们来看下如何取消一个HTTP请求。
取消请求相关的模块在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) { // 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
文件中,有与之相对应的取消请求的代码:
if (config.cancelToken) { // 等待取消 config.cancelToken.promise.then(function onCanceled(cancel) { if (!request) { return; } request.abort(); reject(cancel); // 重置请求 request = null; }); }
结合上面的取消HTTP请求的示例和这些代码,咱们来简单说下相关的实现逻辑:
CancelToken
类的实例A和一个函数cancel。request.abort()
。在以前的章节中有提到过,axios在处理发送请求的dispatchRequest
函数时,没有当作一个特殊的函数来对待,而是采用一视同仁的方法,将其放在队列的中间位置,从而保证了队列处理的一致性,提升了代码的可阅读性。
在adapter的处理逻辑中,axios没有把http和xhr两个模块(一个用于Node.js发送请求,另外一个则用于浏览器端发送请求)当成自身的模块直接在dispatchRequest
中直接饮用,而是经过配置的方法在default.js
文件中进行默认引入。这样既保证了两个模块间的低耦合性,同时又可以为从此用户须要自定义请求发送模块保留了余地。
在取消HTTP请求的逻辑中,axios巧妙的使用了一个Promise来做为触发器,将resolve函数经过callback中参数的形式传递到了外部。这样既可以保证内部逻辑的连贯性,也可以保证在须要进行取消请求时,不须要直接进行相关类的示例数据改动,最大程度上避免了侵入其余的模块。
本文对axios相关的使用方式、设计思路和实现方法进行了详细的介绍。读者可以经过上述文章,了解axios的设计思想,同时可以在axios的代码中,学习到关于模块封装和交互等相关的经验。
因为篇幅缘由,本文仅针对axios的核心模块进行了分解和介绍,若是对其余代码有兴趣的同窗,能够去GitHub进行查看。
若是有任何疑问或者观点,欢迎随时留言讨论。