当前位置: 首页 > 科技观察

一起学习源码Axios

时间:2023-03-13 15:45:30 科技观察

1.理解思路Axios是一个基于Promise的HTTP库。按照官网介绍,它有以下特点:XMLHttpRequests会在浏览器端创建,HTTP请求会在Node端创建。因为axios是一个基于Promise的HTTP库,所以支持PromiseAPI支持请求和响应拦截器支持请求和响应数据转换支持取消请求,自动转换JSON数据客户端支持防御XSRF攻击通过上面官网介绍的特性,我认为它的突出优点有三点一:支持PromiseAPI,可以方便链式调用;支持请求和响应拦截器,将Node中中间件的思想引入库中,可以在发送请求前和收到响应后进行处理。支持数据转换器,转换器主要负责在发送数据前和收到响应后对数据进行处理。2.把握设计在了解了库设计的特点后,下面从源码目录、抽象接口、核心设计原则三个层面对Axios进行整体把握。2.1源码目录如下图是axios的源码目录和各个文件的功能2.2抽象接口对源码目录有了一定的了解后,下面用UML类图进一步了解各个的依赖关系系统的模块,为后续的源码分析打下良好的基础。(看图注意一起看源码)2.3设计原理首先看一段代码,这段代码的执行顺序包含了axios的核心原理。axios.defaults.baseURL='http://localhost:8080'//请求拦截器axios.interceptors.request.use(config=>{console.log('请求拦截器一',config);returnconfig;},error=>{console.log('requestinterceptorrejected1');returnPromise.reject(error);});//请求拦截器2axios.interceptors.request.use(config=>{console.log('请求拦截器2',config);returnconfig;},error=>{console.log('requestinterceptorrejected2');returnPromise.reject(error);});//响应拦截器axios.interceptors.response.use(response=>{console.log('responseinterceptorone',response);returnresponse;},error=>{console.log('responseinterceptorrejected1');returnPromise.reject(error);});//响应拦截器2axios.interceptors.response.use(response=>{console.log('responseinterceptor2',response);returnresponse;},error=>{console.log('响应拦截器拒绝2');返回Promise.reject(错误);});axios('/',{method:'post',headers:{'Content-Type':'application/json'},data:{test:'test'},//请求转换器transformRequest:[(data,headers)=>{console.log('requesttransformer',data);returnJSON.stringify(data)}],//响应转换器transformResponse:[(response,headers)=>{console.log('responseconverter',response);returnresponse;}]}).then((response)=>{console.log(response.data)})写了这么多代码,大家一定对这段代码的执行结果很感兴趣。为了满足观众的好奇心,下面直接抛出结果,但是光看执行结果是无法理解核心设计原理的。急,其实小代码已经包含了axios的整个执行过程。整个过程通过观察结果和代码可以简化成下图:这就是核心原理吗?是的,你没看错,这就是Axios的核心,根据设计原则,通过一系列链式处理,得到需要的结果。3、体验细节说完宏,再详细说说几个核心细节:整个流程,请求/响应拦截器,什么是dispatchRequest,请求/响应数据转换器。3.1整体运行流程核心原理在第二章讲解。退伍军人一定对整个工作原理非常感兴趣。让我来解答你的疑惑——AxiosfunctionAxios(instanceConfig){this.defaults=instanceConfig;//拦截器实例化this.interceptors={request:newInterceptorManager(),response:newInterceptorManager()};}//通过一系列的继承绑定操作,这个函数其实就是axios函数axios.prototype.request=functionrequest(config){//...config=mergeConfig(this.defaults,config);//设置config.method//...//****core****//存储调用链的数组varchain=[dispatchRequest,undefined];varpromise=Promise.resolve(配置);//把请求拦截器的内容放在数组前面(注意unshift函数,很好解释为什么先调用请求拦截器)this.interceptors.request.forEach(functionunshiftRequestInterceptors(interceptor){chain.unshift(拦截器.fulfilled,拦截器.rejected);});//把响应拦截器的内容塞进数组Afterthis.interceptors.response.forEach(functionpushResponseInterceptors(interceptor){chain.push(interceptor.fulfilled,interceptor.rejected);});//使用Promise将整个数组的内容串起来,这样就可以有序的链式执行while(chain.length){promise=promise.then(chain.shift(),chain.shift());}回报承诺;};是不是很聪明?通过先使用数组存储需要的内容,先处理在数组的前面(请求拦截器),后处理在数组的后面(响应拦截器),然后使用Promise将整个内容串起来放在一起,是处理网络请求异步问题的好方法——Perfect3.2请求/响应拦截器通过观察第二部分的执行结果,我们已经了解了请求/响应拦截器。这里总结一下:请求拦截器是在发送请求之前执行的回调函数。个人觉得它最大的功能就是配置多个请求。统一修改仔细观察发现先添加请求拦截器1然后执行,是否符合整体运行过程中的代码。响应拦截器是请求响应后执行的回调函数。成功回调的参数为response响应,可以统一修改多个请求的响应。先抛出请求/响应拦截器函数InterceptorManager()的核心代码{this.handlers=[];}//注册拦截器InterceptorManager.prototype.use=functionuse(fulfilled,rejected){this.handlers.push({fulfilled:完成,拒绝:拒绝});returnthis.handlers.length-1;};//删除拦截器InterceptorManager.prototype.eject=functioneject(id){if(this.handlers[id]){this.处理程序[id]=null;}};//分发拦截器InterceptorManager.prototype.forEach=functionforEach(fn){utils.forEach(this.handlers,functionforEachHandler(h){if(h!==null){fn(h);}});};看看拦截器的核心源码,是不是觉得有点像设计模式?没错,就是观察者模式。调用use方法时,会把回调函数(成功、失败)保存到handlers属性中,方便后面调用;调用eject方法时,对应的索引位置回调函数会被删除;当调用forEach方法时,它会分发handlers属性(存储的拦截器回调)中的内容。3.3什么是dispatchRequest?我们讲了整个请求的pre-request(请求拦截器)和post-request(响应拦截器)。是不是觉得少了点什么?如何发送请求?这是dispatchRequest(config)。module.exports=functiondispatchRequest(config){//...//请求数据转换config.data=transformData(config.data,config.headers,config.transformRequest);//...//获取adapter:自行配置自己选择即可,没有设置则选择默认(浏览器端选择xhrAdapter,节点端选择httpAdapter;这也是axios支持的原因浏览器和Node)varadapter=config.adapter||默认.adapter;returnadapter(config).then(functiononAdapterResolution(response){//...//响应数据转换器response.data=transformData(response.data,response.headers,config.transformResponse);returnresponse;},functiononAdapterRejection(reason){if(!isCancel(reason)){//...//响应数据转换器if(reason&&reason.response){reason.response.data=transformData(reason.response.data,reason.response.标头,config.transformResponse);}}returnPromise.reject(reason);});};通过观察整个请求过程中的中间环节——dispatchRequest,它一共做了三件事:调用请求数据转换converter转换请求数据选择合适的adapter发起请求——自己配置就选择自己的,不自己配置就选择默认的(浏览器端选择xhrAdapter,node端选择httpAdapter;这也是axios同时支持浏览器和Node的原因.)当请求数据返回时,调用响应数据转换器对响应数据进行转换3.4请求/响应数据转换器既然3.3中提到了请求/响应转换器,那我们就在本节//核心源码模块中说说吧。exports=functiontransformData(data,headers,fns){utils.forEach(fns,functiontransform(fn){data=fn(data,headers);});返回数据;};请求数据转换调用,本质上是使用请求数据转换器对请求头和请求数据进行具体处理(transformRequest是一个处理函数数组,defaults包含默认配置)config.data=transformData(config.data,config.headers,config.transformRequest);响应数据转换调用与请求数据转换调用类似,对响应体进行一系列处理(transformResponse是一个处理函数数组,defaults包含默认配置)response.data=transformData(response.data,response.headers,配置.transformResponse);四、结语以上三章从整体上分析了Axios,描述了Axios的特点、总体设计和关键环节。通过阅读源码,学习了很多知识,可以更加熟练的使用Axios。为了保证各位老手学习axios源码的效果,学习axios源码有两个建议:阅读本文和阅读源码,加深理解。不要纠结于具体的实现,从宏观的角度看源码,可以节省很多时间。