前端限流实战:从 429 状态码处理到消除"双重报错"
在开发限流功能时,我们经常会遇到两个阶段的体验问题:第一阶段是前端直接显示生硬的 HTTP 状态码错误,第二阶段是修复后出现"双重错误提示"。本文将带你从前端拦截器的角度,一步步优化限流交互体验。
一、 第一阶段:拒绝生硬的 HTTP 错误
当后端限流切面生效时,会抛出 429 状态码。如果前端没有针对性处理,用户看到的将是 Request failed with status code 429 这种毫无意义的报错。
现象:
后端日志显示触发了限流:
text
接口触发限流:操作太快啦,请稍微休息一下!
但前端仅显示 Axios 的默认网络错误。
原因分析:
前端的 Axios 响应拦截器中没有对 429 状态码进行专门捕获,导致错误直接被抛到底层的默认处理逻辑。
解决方案:
在 common.js 的响应拦截器 error 处理函数中,增加对 429 状态码的判断,提取后端返回的业务错误信息。
javascript
// ==================== B. 异常响应 (401, 429, 500 等) ====================
async function (error) {
const originalRequest = error.config;
const response = error.response;
// -----------------------------------------------------
// 关键修复:拦截 429 状态码并提取后端错误提示
// -----------------------------------------------------
if (response && response.status === 429) {
// 从后端返回的 Result 对象中提取 errorMsg
// 后端结构:{ "success": false, "errorMsg": "...", "data": null }
const msg = (response.data && response.data.errorMsg)
? response.data.errorMsg
: "请求过于频繁,请稍后再试";
// 返回 reject 阻止业务逻辑继续
return Promise.reject(msg);
}
// ... (后续 401 等逻辑保持不变) ...
}
二、 第二阶段:消除"双重报错"的尴尬
应用了上面的代码后,虽然能看到友好提示了,但往往会出现一个新的问题:页面上同时弹出了两个一模一样的错误提示框。
现象:
用户触发限流后,屏幕顶部弹出了两个红色的提示框,内容都是"操作太快啦"。
根源分析:
这是一个典型的"职责不清"问题,导致了冗余操作:
- 全局拦截器 :在捕获到 429 后,为了"保险"起见,手动调用了
ELEMENT.Message.error(msg)进行弹窗。 - 业务代码 :在请求的
.catch()中也写了弹窗逻辑(例如this.$message.error(err))。
当拦截器弹窗并reject后,业务代码再次捕获到错误并弹窗,导致了双重提示。
三、 架构级修复:职责单一原则
在成熟的前端架构中,全局拦截器应遵循**"只清洗数据,不干涉表现"**的原则。
- 拦截器职责:将 HTTP 协议层的错误(如 429、500)转化为业务层可读的字符串,并向下传递。
- 业务层职责 :接收错误信息,并根据当前页面风格决定如何展示(弹窗、Alert、写入日志等)。
最佳实践代码:
我们需要删除拦截器中手动弹窗的代码,只负责提取错误信息。
javascript
// ==================== B. 异常响应 (401, 429, 500 等) ====================
async function (error) {
const originalRequest = error.config;
const response = error.response;
// -----------------------------------------------------
// 429 限流修复:仅负责提取后端错误消息 (不再手动弹窗)
// -----------------------------------------------------
if (response && response.status === 429) {
// 提取后端 Result 对象中的 errorMsg
const msg = (response.data && response.data.errorMsg)
? response.data.errorMsg
: "请求过于频繁,请稍后再试";
// 【架构级修复点】:此处不再调用 ELEMENT.Message.error(msg)
// 理由:业务代码中的 .catch() 已经具备弹窗能力。
// 此处重复调用会导致弹出两条信息。
return Promise.reject(msg);
}
// -----------------------------------------------------
// 401 主动刷新逻辑 (保持原有逻辑)
// -----------------------------------------------------
if (
response &&
response.status === 401 &&
!originalRequest.url.includes("/user/refresh") &&
!originalRequest._retry
) {
// ... (保持你原有的 Token 刷新逻辑不变) ...
}
// -----------------------------------------------------
// 兜底处理:提取其他非 429/401 类型的错误
// -----------------------------------------------------
const finalMsg = (response && response.data && response.data.errorMsg)
? response.data.errorMsg
: (error.message || "系统繁忙");
return Promise.reject(finalMsg);
}
四、 总结
通过这次重构,我们不仅解决了限流提示的问题,更重要的是理清了前端异常处理的边界:
- 数据清洗:拦截器把晦涩的 HTTP 429 错误,变成了友好的中文提示。
- 交互统一 :限流报错现在统一通过业务页面的
.catch()弹出,其样式和位置与"密码错误"、"格式错误"等其他业务报错保持完全一致。
这种设计避免了全局拦截器对 UI 的过度干预,提升了代码的可维护性和扩展性。修改完成后,请务必清除浏览器缓存,再次测试,你将只会看到一个优雅、准确的限流提示。