为什么这么做很有意义?
- 用户体验(UX)灾难:想象一下用户打开“仪表盘”页面,页面同时触发了 5 个接口(获取用户信息、获取图表数据、获取通知、获取菜单等)。如果服务器挂了(500)或者用户断网了,瞬间弹出 5 个红色的“网络错误”弹框,像刷屏一样,用户体验极差。
- 401 登录过期:这是最经典的场景。当 Token 过期时,页面并发的 3 个请求都会返回 401。如果不做防抖,用户会看到 3 个“登录已过期,请重新登录”的弹窗(或者被重定向逻辑触发 3 次),这在逻辑上是冗余的。
这里分两种情况:
- Toast 轻提示(ElMessage) :防止满屏飘红。
- Modal 确认框(ElMessageBox) :防止 401 时弹出多个确认窗口。
我们可以设置一个“锁”,当第一个错误弹出后,几秒钟内不再弹出新的错误。
修改 src/utils/request.ts:
import axios from 'axios'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useUserStore } from '@/store/modules/user'
// 1. 定义一个变量来控制错误提示的频率
let isErrorMessageShowing = false
const service = axios.create({
baseURL: import.meta.env.VITE_API_URL,
timeout: 5000
})
// ... 请求拦截器保持不变 ...
// 响应拦截器
service.interceptors.response.use(
(response) => {
// ... 正常逻辑保持不变 ...
const res = response.data
if (res.code === 200) {
return res.data
} else {
// 调用统一错误处理
showError(res.message || '系统错误')
return Promise.reject(new Error(res.message))
}
},
(error) => {
let msg = '网络请求失败'
if (error.response) {
// 根据状态码判断
switch (error.response.status) {
case 401:
handle401() // 单独处理 401
return Promise.reject(error)
case 403:
msg = '拒绝访问'
break
case 404:
msg = '请求资源不存在'
break
case 500:
msg = '服务器内部错误'
break
default:
msg = error.response.data?.message || '未知错误'
}
}
showError(msg)
return Promise.reject(error)
}
)
/**
* 核心优化:错误提示防抖/节流
* 规则:如果当前有错误正在显示(或在3秒内显示过),则不再弹出新错误
*/
function showError(msg: string) {
if (isErrorMessageShowing) {
return // 如果锁住了,直接返回,不弹窗
}
isErrorMessageShowing = true // 加锁
ElMessage.error({
message: msg,
duration: 3000,
onClose: () => {
// 这里的 onClose 是 Element Plus 消息关闭时的回调
// 你可以选择在这里解锁,或者用 setTimeout 解锁
}
})
// 设置一个冷却时间,比如 3秒内只弹一次错误
setTimeout(() => {
isErrorMessageShowing = false // 解锁
}, 3000)
}
// ...
针对 401 (Token过期) 的“单例模式”
对于 401,我们通常会弹出一个 ElMessageBox.confirm 模态框让用户去登录。这个绝对不能弹多次。
// 定义一个变量标记是否正在重新登录
let isReloginShow = false
function handle401() {
// 如果已经弹出了框,就不要再弹了
if (isReloginShow) return
isReloginShow = true // 加锁
ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 点击确定
isReloginShow = false // 解锁
const userStore = useUserStore()
userStore.logout() // 登出并跳转
}).catch(() => {
// 点击取消
isReloginShow = false // 解锁
})
}
代码逻辑解释:
-
并发场景模拟:
假设页面初始化同时发了 5 个请求,Token 刚好过期了,这 5 个请求几乎同时返回 401。 -
第一个 401 到达:
- isErrorMessageShowing 为 false。
- 执行 showError -> 上锁 -> 弹出“请先登录”。
- 执行 logout()。
-
后续 4 个 401 到达:
- isErrorMessageShowing 已经是 true 了。
- 执行 showError -> 直接 return。
- 用户界面上只看到一条红色的错误提示,体验非常清爽。
-
解锁:
- 3秒后,setTimeout 执行,锁打开,允许下一次错误提示弹出。