分析了axios的源码设计🔗前两天拦截器(interceptor)给axios的扩展留了一个入口,我们在工作中经常对axios进行扩展,例如:取消重复请求,权限验证,失败重试等。那么如何设计实现一个扩展Axios的好拦截器?通过本周下载量100w+的axios-retry的三方库,我们可以学习到它的功能设计,工具库项目的承包策略,以此来引点子来完善我们的编码。设计能力!Github:https://github.com/softonic/axios-retrynpm:https://www.npmjs.com/package/axios-retry1.编写工具库的package.json,查看源码一个模块。首先查看README.md和package.json文件。参考上面,我们以后在开发工具库的时候还要注意以下几个字段:files:es和lib文件夹,以及index.js和index.d.ts文件,在发布包的时候会发布.typings:TypeScript类型定义文件,用于TypeScript编码环境中的智能类型提示,该字段也可以写成types。main:主入口文件,表示当前库导入项目时,默认文件为index.js打包时指定import,例如Rollup和Webpack我们库的ESM版本的文件路径。exports:提供一种方法来展示声明如何为不同的环境和JavaScript样式导入模块,同时限制对其内部部分的访问。本领域提案来自:BareModuleSpecifierResolutioninnode.js[1]throughdependentfieldsandscriptsfield:developmentdependenciesandusedependencies。可以知道,当前项目直接使用Babel作为打包编译工具,执行npmrunrelease发布包,并结合npmscripts的前后执行生命周期,依次执行以下任务:npmrunreleaseexecution关于package.json字段的功能/功能描述,请参考package.json-NPM[2]2.源码分析与package.json文件中的“发送包”命令相关阅读后,可以知道./es/文件夹下的index.mjs是一个函数实现文件。2.1为什么文件名后缀是.mjs?Node.js的原始模块系统是CommonJs(使用require和module.exports语法)。自从Node.js创建以来,ECMAScript模块系统(使用import和export语法)已经成为标准,Node.js也加入并实现了对ES模块系统的支持。Node.js将*.cjs文件视为CommonJS模块,将*.mjs文件视为ECMAScript模块。它会将.js文件视为项目的默认模块系统,即CommonJS,除非package.json声明“type”:“module”。2.2axios-retry的使用axios-retryexportsaxiosRetry()方法:注入拦截器通过在axios单例中加入“拦截器”,扩展自动重试网络请求的功能。axios-retry主要接受两个参数,第一个是axios实例,第二个是axios-retry的配置defaultOptions:defaultOptions:{retries?:number;//自动重试次数shouldResetTimeout?:boolean;//是否重试设置“超时时间”retryCondition?:Function;//重试条件,可以传入一个自定义的判断函数retryDelay?:Function;//重试请求间隔时间的函数}函数配置貌似挺全的,难怪那么受欢迎。2.3请求拦截器设计&实现在请求拦截器中,会初始化状态,更新请求数:axios.interceptors.request.use((config)=>{constcurrentState=getCurrentState(config);//设置timeofthelastrequest//思考🤔:为什么不在getCurrentState()函数中一起设置?currentState.lastRequestTime=Date.now();returnconfig;});/***初始化并返回给定的“request"and"configuration"Retrystate*@param{AxiosRequestConfig}config*@return{Object}*/functiongetCurrentState(config){//从config中获取stateconstcurrentState=config[namespace]||{};//记录个数ofcurrentrequestscurrentState.retryCount=currentState.retryCount||0;//update/writethecurrentrequeststateinconfigconfig[namespace]=currentState;returncurrentState;}通过将axios-retry字段注入axiosconfig作为存储字段请求状态,在axios请求执行链中,当前请求状态可以be随时从axiosconfig获取。另外,我们看到请求拦截器中并没有设置reject函数。或许这里可以加一个reject的响应函数,这样在请求异常后,不需要直接重试请求,因为错误的请求配置肯定是没有意义的对于网络请求,重试请求是没有意义的,并且直接中断退出请求的执行链。关于退出Promise执行链,提供了几个参考讨论:从如何停止Promise链开始[3]Promise链调用和终止[4]2.4Response拦截器设计&实现在拦截器中,只响应reject函数,即即当前拦截器只有在axios响应阶段出现错误(抛出异常)时才会执行。axios.interceptors.response.use(null,async(error)=>{const{config}=error;//无法读取config,然后退出,可能是其他一些异常情况//例如:主动取消请求,是直接抛出的错误if(!config){returnPromise.reject(error);}//从defaultOptions中读取并设置默认值const{retries=3,//默认自动重试3次retryCondition=isNetworkOrIdempotentRequestError,retryDelay=noDelay,shouldResetTimeout=false}=getRequestOptions(config,defaultOptions);constcurrentState=getCurrentState(config);//判断是否重试重试次数currentState.retryCount+=1;constdelay=retryDelay(currentState.retryCount,error);//axios合并默认配置失败是因为循环结构//参考issue:https://github.com/mzabriskie/axios/issues/370fixConfig(axios,config);//shouldResetTimeout默认为false//According与实际请求时间,对比config.timeout,选择最大值设置超时时间//设置最大超时时间小于1ms(XHR请求<=0不算超时)config.timeout=Math.max(config.timeout-lastRequestDuration-delay,1);}config.transformRequest=[(data)=>data];//commonPromisedelay(sleep)的写法//重新发起请求,调用axios(config)//因为不管是什么类型的请求,都会被标准化为axios(config)//兼容axios.prototye.request在应用层转换returnnewPromise((resolve)=>setTimeout(()=>resolve(axios(config)),delay));}returnPromise.reject(error);});总结这是一篇axios源码分析文章的补充,作为常见的axios功能扩展,失败重试axios-retry是一个很好的例子,可以作为以后扩展axios功能的模板.另外,axios-retry直接通过babel打包,借助npm脚本的生命周期,测试、更新版本、打包构建、发布、gitpush也很值得学习。文中提到,在请求拦截器中,可以增加“发起网络请求”前的错误处理。如果出现错误,则直接中断重试过程,避免多次错误请求,节省计算资源。你可以自己尝试让它发生。当然请求是否需要重试是由响应拦截器中的shouldRetry()函数来保证的,但是在axios请求执行链中,响应拦截器总是会在发起网络请求(dispachRequest()事件)后执行,所以这个尝试还是可以研究一下的,对理解Promise执行链大有裨益。参考文献[1]node.js中的BareModuleSpecifierResolution:https://github.com/jkrems/proposal-pkg-exports/[2]package.json-NPM:https://docs.npmjs.com/cli/v8/configuring-npm/package-json[3]从如何停止Promise链开始:https://github.com/xieranmaya/blog/issues/5[4]Promise的链调用和终止:https://cnodejs.org/topic/58385d4927d001d606ac197d
