当前位置: 首页 > Web前端 > HTML

ahooks如何解决用户多次提交的问题?

时间:2023-03-28 10:59:31 HTML

本文是深入浅出ahooks源码系列文章的第四篇。本系列已整理成文档地址。我觉得还不错,点个关注支持一下,谢谢。本文探讨了ahooks的useLockFn。从而讨论一个非常常见的场景,取消重复请求。想象一下场景,有这样一个场景,有一个表单,你可能会提交多次,很可能会导致结果不正确。解决这类问题的方法有很多,比如加上loading,第一次点击后就不能再点击了。另一种方法是在请求异步函数上加一个静态锁来防止并发。这就是ahooks的useLockFn所做的。useLockFnuseLockFn用于给异步函数加一个racelock,防止并发执行。它的源码比较简单,如下图:import{useRef,useCallback}from'react';//用来给异步函数加一个竞争锁,防止并发执行。functionuseLockFn(fn:(...args:P)=>Promise){//现在处于锁定状态constlockRef=useRef(错误的);//返回一个添加了竞争锁的函数returnuseCallback(async(...args:P)=>{//判断请求是否在进行中if(lockRef.current)return;//请求在进行中lockRef.current=true;try{//执行原始请求constret=awaitfn(...args);//请求完成,状态锁设置为falselockRef.current=false;returnret;}catch(e){//请求失败,状态锁设置为falselockRef.current=false;throwe;}},[fn],);}exportdefaultuseLockFn;可以看到它的入参是一个异步函数,返回一个增加了racelock的功能。通过lockRef做一个flag,初始化的时候它的值为false。正在请求的时候,设置为true,这样下次调用该函数时,不执行原函数直接返回,从而达到加锁的目的。缺点是实际的,但缺点是显而易见的。我需要手动添加每个需要添加竞争锁的异步函数。有没有更通用和方便的方法?答案是axios可以自动取消重复请求。axios自动取消重复请求axios取消请求对于原生XMLHttpRequest对象发起的HTTP请求,可以调用XMLHttpRequest对象的abort方法。那么我们项目中常用的axios呢?其实它底层也是用到了XMLHttpRequest对象,它暴露的取消请求的API是CancelToken。可以这样使用:constCancelToken=axios.CancelToken;constsource=CancelToken.source();axios.post('/user/12345',{name:'gopal'},{cancelToken:source.token})source.cancel('操作被用户取消。');//取消请求,参数可选还有一种方法是调用CancelToken的构造函数创建一个CancelToken,具体使用如下:constCancelToken=axios.CancelToken;letcancel;axios.get('/user/12345',{cancelToken:newCancelToken(functionexecutor(c){cancel=c;})});cancel();//如何取消请求自动取消重复请求如何自动取消请求?答案是通过axios的拦截器。请求拦截器:这类拦截器的作用是在请求发送之前统一执行某些操作,比如在请求头中添加token相关的字段。响应拦截器:这类拦截器的作用是在收到服务器响应后统一执行某些操作,比如发现响应状态码为401时自动跳转到登录页面。具体方法如下:第一种步骤是定义几个重要的辅助函数。generateReqKey:用于根据当前请求的信息生成请求Key。只有key相同才会判断为重复请求。这个很重要,可能跟具体的业务场景有关。比如有一种请求,输入框模糊搜索,用户频繁输入关键词,一次发送多个请求。可能先发送的请求可能最后响应,导致实际搜索结果不符合预期。其实你只需要根据url和请求方法判断为重复请求,然后取消之前的请求即可。这里我觉得如果有必要的话,可以暴露一个API给开发者自定义重复的规则。这里我们先根据请求方式、url、参数生成一个唯一的key。functiongenerateReqKey(config){const{方法、url、参数、数据}=config;返回[方法、url、Qs.stringify(params)、Qs.stringify(data)].join("&");}addPendingRequest。用于将当前请求信息添加到pendingRequest对象中。constpendingRequest=newMap();functionaddPendingRequest(config){constrequestKey=generateReqKey(config);config.cancelToken=config.cancelToken||newaxios.CancelToken((cancel)=>{if(!pendingRequest.has(requestKey)){pendingRequest.set(requestKey,cancel);}});}removePendingRequest。检查是否有重复的请求,如果有则取消发送的请求。函数removePendingRequest(config){constrequestKey=generateReqKey(config);如果(pendingRequest.has(requestKey)){constcancelToken=pendingRequest.get(requestKey);cancelToken(requestKey);pendingRequest.delete(requestKey);}}第二步,添加请求拦截器。axios.interceptors.request.use(function(config){removePendingRequest(config);//检查是否有重复请求,如果有则取消发送的请求addPendingRequest(config);//添加当前请求信息到pendingRequest对象returnconfig;},(error)=>{returnPromise.reject(error);});第二步是添加响应拦截器。axios.interceptors.response.use((response)=>{removePendingRequest(response.config);//从pendingRequest对象中移除请求returnresponse;},(error)=>{removePendingRequest(error.config||{});//从pendingRequest对象中移除请求if(axios.isCancel(error)){console.log("Cancelledduplicaterequest:"+error.message);}else{//添加异常处理}returnPromise.reject(error);});至此,我们就完成了通过axios自动取消重复请求的功能。思考与结论虽然重复请求的问题可以通过钩子或者像useLockFn这样的方法在请求函数中加入竞争锁来解决。但这还是需要依赖开发者的习惯。没有一些规则,就很难避免出现问题。通过axios拦截器及其CancelToken函数,我们可以在拦截器中自动取消发送的请求。当然,如果有一些接口需要重复发送请求,我们可以考虑增加白名单功能,这样请求就不会被取消。