Token 刷新与请求队列怎么处理
Token 刷新真正困难的地方不是发送刷新请求,而是多个请求同时失效时如何保持状态一致。
先用白话理解
这篇内容在讲什么?
Token 像登录后的临时通行证。过期时要换新,同时让正在发送的请求排队等待。
问题
多个接口同时 401,重复刷新 Token 甚至死循环,如何处理?
每个失败请求独立刷新,没有共享刷新状态和重试标记。
解决方案
- 刷新请求保持单例
- 其他请求等待同一 Promise
- 成功后重放原请求
- 失败后清空身份
Axios 刷新与重试示例代码
type RetryConfig = AxiosRequestConfig & { _retried?: boolean }
let refreshPromise: Promise<string> | null = null
api.interceptors.response.use(undefined, async (error) => {
const config = error.config as RetryConfig
if (error.response?.status !== 401 || config._retried) {
return Promise.reject(error)
}
config._retried = true
try {
refreshPromise ??= refreshToken()
const token = await refreshPromise
config.headers = { ...config.headers, Authorization: `Bearer ${token}` }
return api.request(config)
} catch (refreshError) {
clearSession()
return Promise.reject(refreshError)
} finally {
refreshPromise = null
}
})刷新动作必须单例化
第一个 401 请求启动刷新流程,后续失败请求进入等待队列,避免并发发送多个刷新请求导致凭据相互覆盖。
队列需要明确成功与失败出口
刷新成功后使用新 Token 重放请求;刷新失败则统一清理身份状态、拒绝队列并引导重新登录,不能让 Promise 永久悬挂。
重试必须避免循环
刷新接口自身、已经重试过的请求和明确无权限的响应都不应再次进入刷新流程。
- 给重试请求增加内部标记
- 刷新接口使用独立请求实例
- 退出登录时主动取消等待队列
结语
判断最终要回到真实体验
技术方案没有脱离场景的标准答案。把用户任务、业务边界、开发成本和长期维护放在一起评估,才有可能做出稳定、清晰且经得起变化的页面。
照着做的步骤
- 第一个 401 启动刷新
- 其他请求进入等待队列
- 成功后使用新 Token 重试
- 失败后统一退出登录
容易踩的坑
- 并发刷新多次
- 失败后队列永久等待
- 请求陷入无限重试
术语不用怕
- Token:识别登录用户的凭据
- 401:身份无效或过期的响应
- 请求队列:暂时等待处理的请求集合