← 返回内容列表
工程实践

Token 刷新与请求队列怎么处理

Token 刷新真正困难的地方不是发送刷新请求,而是多个请求同时失效时如何保持状态一致。

先用白话理解

这篇内容在讲什么?

Token 像登录后的临时通行证。过期时要换新,同时让正在发送的请求排队等待。

问题

多个接口同时 401,重复刷新 Token 甚至死循环,如何处理?

每个失败请求独立刷新,没有共享刷新状态和重试标记。

解决方案
  1. 刷新请求保持单例
  2. 其他请求等待同一 Promise
  3. 成功后重放原请求
  4. 失败后清空身份
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
  }
})
01

刷新动作必须单例化

第一个 401 请求启动刷新流程,后续失败请求进入等待队列,避免并发发送多个刷新请求导致凭据相互覆盖。

02

队列需要明确成功与失败出口

刷新成功后使用新 Token 重放请求;刷新失败则统一清理身份状态、拒绝队列并引导重新登录,不能让 Promise 永久悬挂。

03

重试必须避免循环

刷新接口自身、已经重试过的请求和明确无权限的响应都不应再次进入刷新流程。

  • 给重试请求增加内部标记
  • 刷新接口使用独立请求实例
  • 退出登录时主动取消等待队列

结语

判断最终要回到真实体验

技术方案没有脱离场景的标准答案。把用户任务、业务边界、开发成本和长期维护放在一起评估,才有可能做出稳定、清晰且经得起变化的页面。

照着做的步骤

  1. 第一个 401 启动刷新
  2. 其他请求进入等待队列
  3. 成功后使用新 Token 重试
  4. 失败后统一退出登录

容易踩的坑

  • 并发刷新多次
  • 失败后队列永久等待
  • 请求陷入无限重试

术语不用怕

  • Token:识别登录用户的凭据
  • 401:身份无效或过期的响应
  • 请求队列:暂时等待处理的请求集合