当前位置: 首页 > Web前端 > vue.js

【转】 axios 是如何封装 HTTP 请求的

时间:2023-04-01 11:36:53 vue.js

[转]axios是如何封装HTTP请求的?概述在前端开发中,经常会遇到发送异步请求的场景。一个功能完备的HTTP请求库可以大大降低我们的开发成本,提高开发效率。axios就是这样一个HTTP请求库,近几年非常流行。目前在GitHub上有超过40,000颗星,被许多权威机构推荐。因此,我们有必要了解一下axios是如何设计的,以及如何实现HTTP请求库的封装。在撰写本文时,axios当前版本为0.18.0,我们以该版本为例,阅读分析部分核心源码。axios的所有源文件都位于lib文件夹下,下面提到的路径都是相对于lib的。在这篇文章中,我们主要讨论:如何使用axios。axios的核心模块(request、interceptor、revocation)是如何设计和实现的?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'))})这是一个官方例子。从上面的代码可以看出,axios的用法和jQuery的ajax方法很相似,都是返回一个Promise对象(这里也可以使用成功回调函数,但是更推荐Promise或者await),然后继续跟进。这个例子很简单,我不需要解释。让我们看看如何添加拦截器功能。添加拦截器函数//添加一个请求拦截器。请注意,这里有2个函数-分别用于成功和失败的回调函数。这样设计的原因后面会介绍。axios.interceptors.request.use(function(config){//在发起请求之前执行一些处理任务returnconfig//返回配置信息},function(error){//请求错误时的处理returnPromise.reject(error)})//添加响应拦截器axios.interceptors.response.use(function(response){//处理响应数据returnresponse//返回响应数据},function(error){//处理工作完成响应错误后returnPromise.reject(error)})从上面的代码我们可以知道:在发送请求之前,我们可以处理请求的配置参数(config);请求响应后,我们就可以处理返回的数据了。当请求或响应失败时,我们也可以指定相应的错误处理函数。取消HTTP请求在开发搜索相关模块时,我们经常会频繁发送数据查询请求。一般来说,当我们发送下一个请求时,需要撤销上一个请求。因此,能够撤销相关请求是非常有用的。axios取消请求示例代码如下:constCancelToken=axios.CancelTokenconstsource=CancelToken.source()//例1axios.get('/user/12345',{cancelToken:source.token,})。catch(function(thrown){if(axios.isCancel(thrown)){console.log('请求被取消',thrown.message)}else{//处理错误}})//例2axios.post('/user/12345',{name:'Newname',},{cancelToken:source.token,})//取消请求(信息参数可选).source.cancel('用户取消了请求')从上面的例子看,axios中使用了基于CancelToken的取消请求方案。但是,该提案现已撤回,详见此处。撤销请求的具体实现方法将在后面的源码分析中进行说明。axios核心模块的设计与实现通过上面的例子,相信大家对axios的使用有了一个大概的了解。下面我们就按照模块来分析axios的设计与实现。下图是我将在本文中介绍的源代码文件。如果有兴趣,最好边阅读边克隆相关代码,这样可以加深对相关模块的理解。HTTP请求模块请求模块的代码放在core/dispatchRequest.js文件中。这里我只展示一些关键代码来做一个简单的解释:module.exports=functiondispatchRequest(config){throwIfCancellationRequested(config);//其他源码//默认的adapter是一个模块,可以根据当前环境选择使用Node或者XHR发送请求。var适配器=config.adapter||默认值.适配器;returnadapter(config).then(functiononAdapterResolution(response){throwIfCancellationRequested(config);//其他源代码returnresponse;},functiononAdapterRejection(reason){if(!isCancel(reason)){throwIfCancellationRequested(config);//其他源码returnPromise.reject(reason);});};在上面的代码中,我们可以知道dispatchRequest方法是通过config.adapter获取发送请求模块的。我们还可以通过传递一个符合规范的适配器函数来替换原来的模块(一般情况下,我们不会这样做,但它是一个松散耦合的扩展点)。在defaults.js文件中,我们可以看到相关适配器的选择逻辑——根据当前容器的一些特有属性和构造函数来使用哪个适配器。functiongetDefaultAdapter(){varadapter//在Node.js中只使用进程类型对象if(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文件中。拦截器模块现在让我们看看axios如何处理请求和响应拦截器功能。这就涉及到axios中的统一接口——request函数。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(interceptor){chain.push(interceptor.fulfilled,interceptor.rejected)})while(chain.length){promise=promise.then(chain.shift(),chain.shift())}returnpromise}这个函数是axios发送请求的接口。由于函数实现代码比较长,这里简单讨论一下相关的设计思路:chain是一个执行队列。队列的初始值是一个携带配置(config)参数的Promise对象。在执行队列中,初始函数dispatchRequest用于发送请求。为了对应dispatchRequest,我们加了一个undefined。之所以添加undefined,是为了给Promise提供成功和失败的回调函数,我们从promise=promise.then(chain.shift(),chain.shift());可以看出在下面的代码中。因此,函数dispatchRequest和undefiend可以看成一对函数。在执行队列链中,发送请求的dispatchReqeust函数在中间。它前面有一个请求拦截器,使用unshift方法插入;后面是响应拦截器,使用push方法插入,在dispatchRequest之后。需要注意的是,这些函数是成对的,即一次会插入两个。看了上面的请求函数代码,我们对如何使用拦截器有了大概的了解。接下来,让我们看看如何撤销HTTP请求。取消请求模块与取消请求相关的模块位于Cancel/文件夹中。下面我们来看一下相关的核心代码。首先,让我们看一下基本的Cancel类。它是一个用来记录取消状态的类。具体代码如下:)}Cancel.prototype.__CANCEL__=true在使用CancelToken类时,需要给它传递一个Promise方法来实现HTTP请求的取消。具体代码如下:functionCancelToken(executor){if(typeofexecutor!=='function'){thrownewTypeError('executormustbeafunction.')}varresolvePromisethis.promise=newPromise(functionpromiseExecutor(resolve){resolvePromise=resolve})vartoken=thisexecutor(functioncancel(message){if(token.reason){//已被撤销return}token.reason=newCancel(message)resolvePromise(token.reason)})}CancelToken.source=functionsource(){varcancelvartoken=newCancelToken(functionexecutor(c){cancel=c})return{token:token,cancel:cancel,}}在adapters/xhr.js文件中,取消请求是这样写的:if(config.cancelToken){//等待取消config.cancelToken.promise.then(functiononCanceled(cancel){if(!request){return}request.abort()reject(cancel)//重置请求request=null})}通过上面取消HTTP请求的例子,下面简单讨论一下相关的实现逻辑:在需要取消的请求中,调用CancelToken类的source方法类进行初始化,会得到一个包含CancelToken类的实例A和cancel方法的对象。当source方法返回实例A时,A处于pending状态的promise对象被初始化。将实例A传递给axios后,promise可以作为取消请求的触发器。当调用source方法返回的cancel方法时,实例A中的promise状态由pending变为fulfilled,然后立即触发then回调函数。于是axios的undo方法——request.abort()被触发。axios这样设计有什么好处呢?发送请求函数的处理逻辑如前几章所述。axios并没有把用来发送请求的dispatchRequest函数当做一个特殊的函数。实际上dispatchRequest会放在队列的中间,保证队列处理的一致性和代码的可读性。适配器的处理逻辑在适配器的处理逻辑中,http和xhr模块(一个用于在Node.js中发送请求,一个用于在浏览器中发送请求)在dispatchRequest函数中没有使用,而Each是一个单独的模块,默认通过defaults.js文件中的配置方法导入。这样既保证了两个模块之间的低耦合,也为以后的用户自定义请求发送模块提供了空间。取消HTTP请求的逻辑在取消HTTP请求的逻辑中,axios设计了Promise作为触发器,对外暴露了resolve函数,在回调函数中使用。既保证了内部逻辑的一致性,又保证了在需要撤销请求时,不需要直接更改相关类的样本数据,很大程度上避免了对其他模块的侵入。小结本文详细介绍了axios的用法、设计思想和实现方法。看完之后可以了解axios的设计,理解模块的封装和交互。本文只介绍axios的核心模块。如果您对其他模块代码感兴趣,可以在GitHub上查看。(超过)