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);});//请求拦截器二axios.interceptors.request.use(config=>{console.log('请求拦截器二',config);returnconfig;},error=>{console.log('requestinterceptorrejected2');returnPromise.reject(error);});//响应拦截器axios.interceptors.response.use(response=>{console.log('响应拦截器一',response);returnresponse;},error=>{console.log('responseinterceptorrejected1');returnPromise.reject(error);});//响应拦截器二axios.interceptors.response.use(response=>{console.log('响应拦截器二',response);returnresponse;},error=>{console.log('responseinterceptorrejected2');returnPromise.reject(error);});axios('/',{method:'post',headers:{'Content-Type':'application/json'},data:{test:'test'},//请求转换器transformRequest:[(data,headers)=>{console.log('请求转换器',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);//Setconfig.method//...//****core****//存储调用的数组chainvarchain=[dispatchRequest,undefined];varpromise=Promise.resolve(config);//把请求拦截器的内容放在数组前面(注意unshift函数,它解释了为什么请求拦截器先调用后执行)this.interceptors.request.forEach(functionunshiftRequestInterceptors(interceptor){chain.unshift(interceptor.fulfilled,interceptor.rejected);});//把响应拦截器的内容塞到数组this.interceptors.response后面。forEach(functionpushResponseInterceptors(interceptor){chain.push(interceptor.fulfilled,interceptor.rejected);});//用Promise把整个数组的内容串起来,这样就可以按顺序串起来了while(chain.length){promise=promise.then(chain.shift(),chain.shift());}returnpromise;};是不是很聪明?通过先用数组存储需要的内容,先处理在数组前面(请求拦截器),后处理在数组后面(响应拦截器),然后用Promise把整个内容串起来,很好的处理网络请求的异步问题——Perfect3.2请求/响应拦截器通过观察第二部分的执行结果,我们已经了解了请求/响应拦截器,下面做一个总结:请求拦截器是发送请求前执行的回调函数。个人觉得它最大的作用就是统一修改多个请求的配置。仔细观察发现,请求拦截器1是先添加后执行的。跟整体的操作流程有关系吗?代码是对的。响应拦截器是请求响应后执行的回调函数。成功回调的参数为response响应,可以统一修改多个请求的响应。先抛出请求/响应拦截器functionInterceptorManager(){this.handlers=[];}//注册拦截器InterceptorManager.prototype.use=functionuse(fulfilled,rejected){this.handlers.push({fulfilled:fulfilled,拒绝:拒绝});returnthis.handlers.length-1;};//删除拦截器InterceptorManager.prototype.eject=functioneject(id){if(this.handlers[id]){this.handlers[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);//...//获取适配器:configure自己选择吧,如果没有任何设置就选择默认(浏览器端选择xhrAdapter,节点端选择httpAdapter;这就是axios同时支持浏览器和Node的原因)varadapter=config.adapter||defaults.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.headers,config.transformResponse);}}returnPromise.reject(reason);});};通过观察整个请求过程中的中间环节——dispatchRequest,它做了三件事ngstotal:调用requestdataconverter转换请求数据选择合适的adapter发起Request-自己配置就选自己的,不自己配置就选默认的(浏览器端选xhrAdapter,httpAdapter为节点端;这就是Axios同时支持浏览器和Node的原因)当请求数据返回时,调用响应数据转换器对响应数据进行转换3.4Request/ResponseDataConverter由于3.3中提到了request/responseconverter,所以本节再说说//核心源码module.exports=functiontransformData(data,headers,fns){utils.forEach(fns,functiontransform(fn){data=fn(data,headers);});returndata;};请求数据转换调用,本质上是利用请求数据转换器来request头和请求数据具体处理(transformRequest是一个处理函数数组,defaults包含默认配置)config.data=transformData(config.data,config.headers,config.transformRequest);响应数据转换调用类似于请求数据转换调用,对响应体进行一系列处理(transformResponse是一个处理函数数组,defaults包含默认配置)response.data=transformData(response.data,response.headers,config.transformResponse);4.结论以上三章本章从整体上分析了Axios,描述了Axios的特点、总体设计和关键环节。通过阅读源码,学到了很多知识,可以更加熟练的使用Axios。为了保证各位老手学习axios源码的效果,学习axios源码有两个建议:阅读本文和阅读源码,加深理解。不要纠结于具体的实现,从宏观的角度看源码,可以节省很多时间。
