当前位置: 首页 > 后端技术 > Node.js

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

时间:2023-04-03 13:56:42 Node.js

后台并发冲突问题,这是日常开发中比较常见的问题。不同的用户在较短的时间间隔内更改数据,或者某个用户的重复提交操作可能会导致并发冲突。并发场景在开发测试阶段难以全面排查,在线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){//处理响应数据returnresponse;},function(error){//处理响应错误returnPromise.reject(error);});2.canceltoken:调用canceltoken接口取消请求。官网提供了两种构建canceltoken的方式,我们采用这种方式:通过传递一个executor函数给CancelToken的构造函数来创建一个canceltoken,这样在上面的请求拦截器中可以立即检测到重复的请求:constCancelToken=axios.CancelToken;letcancel;axios.get('/user/12345',{cancelToken:newCancelToken(functionexecutor(c){//执行器函数接收一个取消函数作为参数cancel=c;})});//取消requestcancel();本文提供的思路是利用axios拦截器API拦截请求,检测是否有多个相同的请求同时处于pending状态,如果有则调用canceltokenAPI取消重复请求。如果用户重复点击按钮,提交了两个相同的请求,A和B(考虑请求路径、方法、参数),我们可以选择以下拦截方案之一:取消A请求,只发出B请求CancelB请求,只发出A请求取消B请求,只发出A请求,将收到的A请求的返回结果作为B请求的返回结果第三种方案需要做监听处理增加复杂度,结合我们实际的业务最后使用第二种方案实现需求,即只发送第一个请求。当A请求还处于pending状态时,取消所有跟A重复的后续请求,真正发出的只有A请求,直到A请求结束(成功/失败)才停止拦截本次请求.要实现所有挂起状态请求的存储,首先我们需要将项目中所有挂起状态请求存储在一个变量中,叫做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对象的目的是为了方便我们查询是否包含某个key,以及增删key。添加key的时候,对应的value可以设置一些自定义的函数参数,后面扩展函数的时候会用到。config是axios拦截器中的一个参数,里面包含了当前请求的信息。在发送请求之前,检查当前请求是否重复。在请求拦截器中,生成上面的requestKey,检查pendingRequests对象中是否包含当前请求的requestKey:说明是重复请求,取消当前请求:将requestKey添加到pendingRequests对象中,因为当前请求的requestKey也是在后续的响应拦截器中使用。为了避免踩坑,最好不要再次生成,这一步将requestKey保存回axios拦截器的config参数中,直接通过响应拦截器中的response.config.requestKey获取即可.代码示例://请求拦截器axios.interceptors.request.use((config)=>{if(pendingRequests.has(requestKey)){config.cancelToken=newaxios.CancelToken((cancel)=>{//取消函数的参数将被捕获为承诺错误cancel(`重复请求被主动拦截:${requestKey}`);});}else{pendingRequests.set(requestKey,config);config.requestKey=requestKey;}returnconfig;},(error)=>{//这里的错误可能是网络波动引起的,清除pendingRequests对象pendingRequests.clear();returnPromise.reject(error);});请求返回后维护pendingRequests对象如果请求已经成功到达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);})需要清除pend当ingRequests对象的场景遇到由于网络波动或超时等原因导致的请求错误时,需要清空原来存储的所有pending状态的请求记录。上面演示的代码已被注释。另外,之前缓存的也需要在页面切换的时候清空。pendingRequests对象可以使用VueRouter的beforeEach钩子:router.beforeEach((to,from,next)=>{request.clearRequestList();next();});函数扩展统一处理接口错误提示,并与后台约定接口返回数据的格式。对于接口错误的情况,可以在响应拦截器中添加toast提醒用户。对于不需要报错的特殊接口,可以设置一个参数,存放在axios拦截器的config参数中,过滤掉错误信息://当接口返回的retcode不为0时,报错需要报告。如果请求设置noError为true,接口不会报错真,消息:response.data.message,类型:'error',});}returnPromise.reject(response.data);}发送请求时给控件添加加载效果上面使用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();}和上面的filter报错方法类似,在发送请求时,在axios拦截器的config参数中存储元素的类名或id,在请求拦截器中调用addLoading方法,在响应拦截器中调用closeLoading方法,这样就可以指定控件在请求挂起过程中(如按钮)加载,控件在请求结束后自动取消加载效果。支持多种拦截器组合简单看axios拦截器部分的源码就可以理解。它支持定义多个拦截器,所以只要我们定义的拦截器符合Promise.then链式调用的规范,我们就可以添加更多的功能:this.interceptors.request.forEach(functionunshiftRequestInterceptors(interceptor){chain.unshift(interceptor.fulfilled,interceptor.rejected);});this.interceptors.response.forEach(functionpushResponseInterceptors(interceptor){chain.push(interceptor.fulfilled,拦截器.rejected);});while(chain.length){promise=promise.then(chain.shift(),chain.shift());}总结并发问题很常见,处理起来也比较麻烦,前端解决并发的情况避免冲突,可以使用axios拦截器统一处理重复请求,简化业务代码。同时,axios拦截器支持更多的应用。本文提供一些常用扩展函数的实现。有兴趣的同学可以继续挖掘补充拦截器的其他用法。今天的内容就这些了,希望对大家有所帮助。如果觉得内容有帮助,可以关注我的公众号,及时了解最新动态,共同学习!