
导读公司后台页面用户狂点刷新按钮同一接口同一参数 3 秒内发出去 20 个请求后端扛不住返回 502接口超时 30 秒不提示用户以为卡死了。后来把 Axios 封装了重复请求自动取消 超时重试联调效率直接翻倍。代码从项目里扒出来可直接用。Axios 请求层封装重复请求自动取消 超时重试省了一半联调时间先说两个真实场景。第一个运营小姐姐连点导出报表三次后端收到 3 个导出请求全部在跑最后数据库连接池打满接口 502。第二个某个查询接口偶发慢超过默认超时时间前端没有任何提示用户干等。第一步拦截器统一处理响应结构先定义接口返回规范后端统一{ code, message, data }// request.jsimportaxiosfromaxiosimport{ElMessage}fromelement-plusimportrouterfrom/routerconstserviceaxios.create({baseURL:import.meta.env.VITE_API_BASE,timeout:15000})// 响应拦截器统一处理业务码service.interceptors.response.use((response){constresresponse.dataif(res.code200){returnres.data}// 401 未登录跳登录页if(res.code401){router.push(/login)returnPromise.reject(newError(登录已过期))}ElMessage.error(res.message||请求失败)returnPromise.reject(newError(res.message))},(error){ElMessage.error(error.message||网络异常)returnPromise.reject(error)})第二步重复请求自动取消用户重复点击、连发相同请求用 CancelToken 取消前面的// 用一个 Map 存进行中的请求key method url paramsconstpendingMapnewMap()functiongetRequestKey(config){return[config.method,config.url,JSON.stringify(config.params||config.data)].join()}// 请求前取消相同 key 的旧请求service.interceptors.request.use((config){constkeygetRequestKey(config)// 旧的没完成的先取消if(pendingMap.has(key)){pendingMap.get(key).abort()pendingMap.delete(key)}config.cancelTokennewaxios.CancelToken((cancel){pendingMap.set(key,{cancel,abort:cancel})})returnconfig})// 请求结束从 Map 里移除functionremovePending(config){constkeygetRequestKey(config)pendingMap.delete(key)}响应拦截器里调用removePending(config)清理。效果同一个导出报表请求重复点击只有最后一个会发出去前面全被取消后端不再被轰炸。注意取消的请求会走 error 分支要在拦截器里区分取消和真错误(error){if(axios.isCancel(error)){returnPromise.reject(newaxios.Cancel(请求已取消))// 静默处理不弹报错}ElMessage.error(error.message||网络异常)returnPromise.reject(error)}第三步超时重试仅限 GET 查询POST 写操作不能盲目重试可能重复下单重试只开放给 GET// 封装带重试的请求方法asyncfunctionrequestWithRetry(config,retries2){try{returnawaitservice(config)}catch(err){// 取消的请求、业务错误不重试if(axios.isCancel(err)||err.config?.isBusinessError){throwerr}// 网络错误或超时才重试if(retries0(err.codeECONNABORTED||!err.response)){awaitnewPromise(rsetTimeout(r,1000))// 等 1 秒再重试returnrequestWithRetry(config,retries-1)}throwerr}}// 用法查询接口constjobsawaitrequestWithRetry({url:/job/admin/page,method:get,params:{page:1,size:10}})超时重试只针对 GET且最多重试 2 次间隔 1 秒避免雪崩式重试。第四步路由切换时一键取消所有挂起的请求列表页切到详情页上一页的请求还在飞返回时结果才回来页面数据错乱。路由守卫里把 pending 全部取消// 取消所有进行中的请求functioncancelAllPending(){pendingMap.forEach((item){item.abort()})pendingMap.clear()}// 路由守卫切换页面时取消未完成的请求router.beforeEach((to,from,next){cancelAllPending()next()})页面卸载、路由切换时取消挂起请求既省流量也避免数据串页。踩坑一超时重试把加载中遮罩卡死了现象接口超时触发重试页面 loading 遮罩第一次就关闭了重试期间页面无遮罩用户以为没反应又点了一次。排查过程loading 是我在请求前loading.value true、finally 里false但重试是递归调用外层 finally 把 loading 关了重试的请求没有遮罩。定位思路loading 状态应该跟着整个请求生命周期走而不是单次请求。最终解决把 loading 控制放到业务层请求层不管 UIasyncfunctionloadJobs(){loading.valuetruetry{list.valueawaitrequestWithRetry({url:/job/admin/page,method:get,params:{...}})}finally{loading.valuefalse}}请求层专注请求UI 状态业务层管职责清晰不打架。踩坑二取消的请求被当成业务错误疯狂弹窗现象快速切换筛选条件连续弹了四五个错误提示。排查过程请求取消后 axios 抛的是 Cancel 错误我的错误拦截器没判断直接ElMessage.error。定位思路取消不是错误必须静默。最终解决上面代码里的axios.isCancel(err)分支取消请求直接 reject 不弹窗问题消失。可直接复用的要点响应统一{ code, message, data }401 统一跳登录业务错误统一 toast。重复请求用 CancelToken Map 去重key method url 参数后发取消前发。取消的请求必须axios.isCancel静默处理别弹错。路由切换时 cancelAllPending 清空挂起请求防数据串页。超时重试只给 GET最多 2 次间隔 1 秒POST 写操作禁重试。loading 状态放业务层别放请求层跟着整个请求生命周期走。请求层只干请求的活统一参数、统一错误、去重、重试UI 层别掺和。