概述在前端开发过程中,我们经常会遇到需要发送异步请求的情况。使用一个功能齐全、接口完善的HTTP请求库,可以大大降低我们的开发成本,提高我们的开发效率。axios是近几年非常流行的HTTP请求库。目前在GitHub上有超过40K的star,得到了大家的推荐。今天,我们就来看看axios是如何设计的,以及它有哪些值得我们学习的地方。写这篇文章的时候,axios的版本是0.18.0。下面以该版本代码为例,进行具体的源码阅读和分析。目前axios所有的源代码文件都在lib文件夹下,所以我们下面说的路径都是指lib文件夹下的路径。这篇文章的主要内容是:如何使用axiosaxios的核心模块是如何设计和实现的(request,interceptor,withdraw)axios的设计有哪些值得学习的地方如何使用axios要了解axios的设计,我们首先需要来看看axios是怎么使用的。我们用一个简单的例子来介绍下面的axiosAPI。发送请求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(或者通过成功的回调,但是推荐使用Promise或者await)来继续后面的操作。这段代码示例很简单,就不赘述了,我们来看看如何添加过滤功能。添加拦截器(Interceptors)函数//添加一个请求拦截器,注意这里有2个函数,一个处理成功,一个处理失败,后面会解释这种情况的原因axios.interceptors.request.use(function(config){//请求发送前处理returnconfig;},function(error){//PromisereturnPromise.reject(error);});//添加响应拦截器axios.interceptors.response.use(function(response){//处理响应数据returnresponse;},function(error){//响应错误处理returnPromise.reject(error);});通过上面的例子我们可以知道:在发送请求之前,我们可以针对请求的config参数进行数据处理;响应请求后,我们还可以对返回的数据进行具体的操作。同时我们可以在请求失败和响应失败的时候进行具体的错误处理。取消HTTP请求在完成搜索相关功能时,我们往往需要发送频繁的数据查询请求。一般来说,当我们发送下一个请求时,需要取消上一个请求。因此,取消请求相关的功能也是一个优势。axios取消请求示例代码如下:constCancelToken=axios.CancelToken;constsource=CancelToken.source();axios.get('/user/12345',{cancelToken:source.token}).catch(function(thrown){if(axios.isCancel(thrown)){console.log('Requestcanceled',thrown.message);}else{//handleerror}});axios.post('/user/12345',{name:'newname'},{cancelToken:source.token})//取消请求(消息参数可选)source.cancel('Operationcanceledbytheuser.');从上面的例子我们可以看出axios使用了一个基于CancelToken的取消提议。但是,该提案现已撤回,详情可在此处找到。具体的提现实现方法将在后面章节的源码分析中进行说明。axios的核心模块是如何设计实现的通过上面的例子,相信大家对axios的使用方法有了一个大概的了解。下面我们就按照模块来分析axios的设计与实现。下图是本篇博客涉及到的axios相关文件。读者有兴趣可以结合博客通读clone相关代码,加深对相关模块的理解。HTTP请求模块是核心模块,axios发送请求相关的代码位于core/dispatchReqeust.js文件中。由于篇幅有限,下面我会选取一些关键的源码做简单的介绍:module.exports=functiondispatchRequest(config){throwIfCancellationRequested(config);//其他源码//defaultadapter是一个判断当前环境的工具选择是使用Node还是XHR请求发送模块varadapter=config.adapter||defaults.adapter;returnadapter(config).then(functiononAdapterResolution(response){throwIfCancellationRequested(config);//其他源码returnresponse;},functiononAdapterRejection(reason){if(!isCancel(reason)){throwIfCancellationRequested(config);//其他源码returnPromise.reject(原因);});};通过上面的代码和例子我们可以知道dispatchRequest方法是通过获取config.adapter来获取发送请求的模块,我们也可以通过传入符合规范的适配器函数来替换原来的模块(虽然这是一般不做,可以看作是一个松耦合的扩展点)。在default.js文件中,我们可以看到相关的适配器选择逻辑,就是根据当前容器特有的一些属性和构造函数来判断。functiongetDefaultAdapter(){varadapter;//只有Node.js才有变量类型为process的classif(typeofprocess!=='undefined'&&Object.prototype.toString.call(process)==='[objectprocess]'){//Node.js请求模块adapter=require('./adapters/http');}elseif(typeofXMLHttpRequest!=='undefined'){//浏览器请求模块adapter=require('./adapters/xhr');}returnadapter;}axios中的XHR模块比较简单。它是XMLHTTPRequest对象的封装。我们这里就不介绍了。感兴趣的同学可以自行阅读。该代码位于adapters/xhr.js文件中。拦截器模块理解了dispatchRequest实现的HTTP请求发送模块,下面看看axios是如何处理请求和响应拦截功能的。我们看一下axios中请求的统一入口请求函数。axios.prototype.request=functionrequest(config){//其他代码varchain=[dispatchRequest,undefined];varpromise=Promise.resolve(config);this.interceptors.request.forEach(functionunshiftRequestInterceptors(interceptor){chain.unshift(interceptor.fulfilled,interceptor.rejected);});this.interceptors.response.forEach(functionpushResponseInterceptors(拦截器){chain.push(interceptor.fulfilled,interceptor.rejected);});while(chain.length){promise=promise.then(chain.shift(),chain.shift());}returnpromise;};该函数是axios发送请求的入口。由于函数实现比较长,这里简单说一下相关的设计思路:chain是一个执行队列。这个队列的初始值是一个带有配置参数的Promise。在链式执行队列中,插入了初始发送请求函数dispatchReqeust和对应的undefined。后面需要加一个undefined,因为在Promise中,需要成功和失败的回调函数,从代码中可以看出promise=promise.then(chain.shift(),chain.shift());因此,dispatchReqeust和undefined我们可以成为一对函数。在链式执行队列中,发送请求的函数dispatchReqeust在中间。前面是请求拦截器,通过unshift方法放置;后面是响应拦截器,通过push放置。需要注意的是,这些函数是成对放置的,即一次放置两个。通过上面的请求代码,我们大致知道了拦截器的使用方法。接下来,让我们看看如何取消HTTP请求。取消请求模块取消请求相关模块在取消/文件夹中。我们来看看相关的关键代码。首先,让我们看一下元数据Cancel类。它是一个用来记录取消状态的类。具体代码如下:;};Cancel.prototype.__CANCEL__=true;在CancelToken类中,它通过传递一个Promise方法来实现HTTP请求取消。我们看一下具体代码:);vartoken=this;executor(functioncancel(message){if(token.reason){//Cancellationhasalreadybeenrequestedreturn;}token.reason=newCancel(message);resolvePromise(token.reason);});}CancelToken.source=functionsource(){varcancel;vartoken=newCancelToken(functionexecutor(c){cancel=c;});return{token:token,cancel:cancel};};在adapter/xhr.js文件中,有对应取消请求的代码:if(config.cancelToken){//等待取消config.cancelToken.promise.then(functiononCanceled(cancel){if(!request){return;}request.abort();reject(cancel);//重置请求重新quest=null;});}结合上面取消HTTP请求的例子和这些代码,简单说一下相关的实现逻辑:在可能需要取消的请求中,我们在初始化的时候调用了source方法,这个方法返回CancelToken类的一个实例A和一个函数cancel在source方法返回的实例A中初始化一个处于挂起状态的promise。我们将整个实例A传递给axios后,这个promise就作为取消请求的触发器。当source方法返回的cancel方法被调用时,实例A中的promise状态由pending变为fulfilled,立即触发then的回调函数,从而触发axios的取消逻辑——request.abort()。axios的设计有什么值得借鉴的?发送请求函数的处理逻辑在上一章中有提到。axios在处理发送请求的dispatchRequest函数时,并没有把它当作一个特殊的函数,而是采用了一个equal的方法,放在队列中间,从而保证了队列处理的一致性,提高了代码的可读性。Adapter的处理逻辑在adapter的处理逻辑中,axios并没有直接在dispatchRequest中把http和xhr这两个模块(一个给Node.js发送请求,一个给浏览器发送请求)当作自己的模块,而是通过配置方式在default.js文件中默认引入。这样既保证了两个模块之间的低耦合,也为用户以后自定义请求发送模块留有余地。取消HTTP请求的处理逻辑在取消HTTP请求的逻辑中,axios巧妙的使用了一个Promise作为触发器,通过callback中的参数对外传递resolve函数。这样既可以保证内部逻辑的连续性,又可以保证在需要取消请求时,不需要直接修改相关类的样本数据,最大程度的避免了对其他模块的侵入。小结本文详细介绍了axios相关的用法、设计思路和实现方法。读者可以通过以上文章了解axios的设计思路,同时学习axios代码中模块封装和交互的相关经验。由于篇幅原因,本文只分解介绍axios的核心模块。如果你对其他代码感兴趣,可以去GitHub查看。
