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

使用axios拦截器解决“前端并发冲突”

时间:2023-03-12 21:17:19 科技观察

后台并发冲突问题,这是日常开发中比较常见的问题。不同的用户在较短的时间间隔内更改数据,或者某个用户的重复提交操作可能会导致并发冲突。并发场景在开发测试阶段难以全面排查,在线BUG出现后难以定位。所以做好并发控制是前后端开发过程中需要注意的问题。对于短时间内同一用户重复提交数据的问题,前端通常可以先做一层拦截。本文将讨论前端如何使用axios拦截器过滤重复请求,解决并发冲突。一般处理方式——每次发送请求添加加载在尝试axios拦截器之前,先看看我们之前的业务是如何处理并发冲突的:用户每次操作页面上的某个控件(输入框、按钮等)),backward客户端发送请求时,在页面相应控件上添加加载效果,提示数据加载正在进行中,同时也防止用户在加载效果结束前继续操作该控件。这是最直接有效的方法。如果你的前端团队成员足够细心和耐心,并且有良好的编码习惯,你可以解决大部分用户粗心的重复提交导致的并发问题。更好的解决方案:axios拦截器在一个统一的项目中处理了这么多需要前端限制并发的场景。当然,我们还得想一个更好更省事的方案。由于每次发送请求都会进行并发控制,如果能将发送请求的public函数重新封装,统一处理重复请求,实现自动拦截,我们的业务代码就可以大大简化。项目使用的axios库用于发送http请求。Axios官方为我们提供了丰富的API。下面我们来看一下拦截请求所需要的两个核心API:1.interceptors拦截器包括请求拦截器和响应拦截器,可以在请求发送前或者响应后进行拦截处理,用法如下://添加请求拦截器axios.interceptors.request.use(function(config){//在发送请求之前做一些事情returnconfig;},function(error){//对请求错误做一些事情returnPromise.reject(error);});//添加响应拦截器axios.interceptors.response.use(function(response){//Dosomethingabouttheresponsedatareturnresponse;},function(error){//DosomethingabouttheresponseerrorreturnPromise.reject(error);});2.canceltoken:调用canceltoken接口取消请求。官网提供了两种构建canceltoken的方式,我们采用这种方式:通过传递一个executor函数给CancelToken的构造函数来创建一个canceltoken,这样在上面的请求拦截器中可以立即检测到重复的请求:constCancelToken=axios.CancelToken;letcancel;axios.get('/user/12345',{cancelToken:newCancelToken(functionexecutor(c){//执行函数接收一个取消函数作为参数cancel=c;})});//canceltherequestcancel();本文提供的思路是利用axios拦截器API拦截请求,检测是否有多个相同的请求同时处于pending状态,如果有则调用canceltokenAPI取消重复的请求。如果用户重复点击按钮,提交了两个相同的请求,A和B(考虑请求路径、方法、参数),我们可以选择以下拦截方案之一:取消A请求,只发出B请求CancelB请求,只发出A请求取消B请求,只发出A请求,将收到的A请求的返回结果作为B请求的返回结果第三种方案需要做监听处理增加复杂度,结合我们实际的业务最后使用第二种方案实现需求,即只发送第一个请求。当A请求还处于pending状态时,取消所有跟A重复的后续请求,真正发出的只有A请求,直到A请求结束(成功/失败)才停止拦截本次请求.具体实现1.存储所有挂起的状态请求首先,我们需要将项目中所有挂起的状态请求存储在一个变量中,叫做pendingRequests,可以通过封装axios,或者定义全局变量,将其封装成一个单例类,以保证pendingRequests变量可以在每次发送请求之前访问,并检查它是否是重复请求。letpendingRequests=newMap()将每个请求的方法、url和参数组合成一个字符串,作为标识请求的唯一键,也是pendingRequests对象的键:constrequestKey=`${config.url}/${JSON.stringify(config.params)}/${JSON.stringify(config.data)}&request_type=${config.method}`;帮助理解的Tips:将pendingRequests定义为一个map对象的目的是为了方便我们查询是否包含Akey,以及增删key。添加key的时候,对应的value可以设置一些自定义的函数参数,后面扩展函数的时候会用到。config是axios拦截器中的一个参数,里面包含了当前请求的信息2.发送请求前在请求拦截器中检查当前请求是否重复,生成上面的requestKey,查看pendingRequests对象中是否包含requestKey的requestKey当前请求是:说明是对于重复请求,取消当前请求:将requestKey添加到pendingRequests对象中,因为当前请求的requestKey也会在后续的响应拦截器中使用。为了避免踩坑,最好不要再生成了。这一步将requestKey存回axios拦截器的config参数中,然后在response拦截器中直接通过response.config.requestKey获取即可。代码示例://请求拦截器axios.interceptors.request.use((config)=>{if(pendingRequests.has(requestKey)){config.cancelToken=newaxios.CancelToken((cancel)=>{//cancelfunction的参数将被捕获为承诺错误cancel(`重复请求被主动拦截:${requestKey}`);});}else{pendingRequests.set(requestKey,config);config.requestKey=requestKey;}returnconfig;},(error)=>{//这里的错误可能是网络波动引起的,清除pendingRequests对象pendingRequests.clear();returnPromise.reject(error);});3、请求返回后维护pendingRequests对象如果request我们成功到了response拦截器这一步,说明请求已经结束了pending状态,那么我们需要从pendingRequests中移除:axios.interceptors.response.use((response)=>{constrequestKey=response.config.requestKey;pendingRequests.delete(requestKey);returnPromise.resolve(response);},(error)=>{if(axios.isCancel(error)){console.warn(error);returnPromise.reject(error);}pendingRequests.clear();returnPromise.reject(error);})4.需要清除pendingRequests对象的场景遇到网络波动或超时导致请求错误时,需要清除最初存储的所有未决请求记录。上面演示的代码已经做了注解。另外,之前缓存的pendingRequests对象也需要在页面切换时清空。你可以使用VueRouter的beforeEach钩子:router.beforeEach((to,from,next)=>{request.clearRequestList();next();});功能拓展1、接口错误提示统一处理,后台约定接口返回数据格式。对于接口错误的情况,可以在响应拦截器中添加toast提醒用户。对于不需要报错的特殊接口,可以设置一个参数存放在axios拦截器的config参数中,过滤掉报错信息://当接口返回retcode不为0时,报错需要被报告。如果请求设置noError为true,接口不会报错,message:response.data.message,type:'error',});}returnPromise.reject(response.data);}2.发送请求时给控件添加加载效果。上面使用axios拦截器过滤重复请求时,可以在控制台抛出信息提示开发者。在此基础上,如果能给页面操作的控件加上加载效果会更加人性化。常用的ui组件库提供加载服务,可以指定页面上需要添加加载效果的控件。下面是一个以elementUI为例的示例代码://给loadingTarget对应的控件添加加载效果,存储loadingService实例addLoading(config){if(!document.querySelector(config.loadingTarget))return;config.loadingService=加载中。service({target:config.loadingTarget,});}//调用loadingService实例的close方法关闭对应元素的加载效果closeLoading(config){config.loadingService&&config.loadingService.close();}类似对上面的过滤报错方法发送请求时,在axios拦截器的config参数中存入元素的类名或id,在请求拦截器中调用addLoading方法,在响应拦截器中调用closeLoading方法,所以可以指定控件在请求挂起过程中(如按钮)加载,请求结束后控件自动取消加载效果。支持多种拦截器组合简单看axios拦截器部分的源码就可以理解。它支持定义多个拦截器,所以只要我们定义的拦截器符合Promise.then链式调用的规范,我们就可以添加更多的功能:this.interceptors.request.forEach(functionunshiftRequestInterceptors(拦截器){chain.unshift(interceptor.fulfilled,interceptor.rejected);});this.interceptors.response.forEach(functionpushResponseInterceptors(拦截器){chain.push(interceptor.fulfilled,拦截器。拒绝了);});while(chain.length){promise=promise.then(chain.shift(),chain.shift());}总结并发问题很常见,处理起来也比较繁琐。前端解决并发冲突时,可以使用axios拦截器统一处理重复请求,简化业务代码。同时,axios拦截器支持更多的应用。本文提供一些常用扩展函数的实现。有兴趣的同学可以继续挖掘补充拦截器的其他用法。今天的内容就这些了,希望对大家有所帮助。