tql,寥寥几行,实现无队列无感刷新

前言

可能大家在项目中已经都用上了双 token,可能你的 refresh_token 至今为止都没派上用场。

你可能已经看过了很多篇讲 token 无感刷新的文章了,看完的你可能有三种情况

  1. 不知所云
  2. 又臭又长,懒得看
  3. 管理一个重试队列(queue)

相信聪明又耐心的你们,大多处在第三种情况。

而这篇文章特别短,将在最快的时间内教会你一种 十分简单且更高效 的刷新方式。

无队列无感刷新

要说无队列无感刷新前,要先了解什么是队列式无感刷新,请看下文

队列式 无感刷新机制

定义一个标志位,用于检查是否正在执行刷新行为,再定义一个数组,用来存放 401 的请求,在拦截器中拦截到时去请求刷新,如果标志位为 false,则去请求刷新并将标志位设置为 true、将请求 push 进队列,如果标志位为 true(第二个 401 请求)则直接 push 到队列并返回。等到刷新完成后,遍历重试队列。

它的代码大概长这样:

TypeScript 复制代码
let isRefreshing = false
let requestQueue: ((token: string) => void)[] = []

axiosInstance.interceptors.response.use(undefined, async (error) => {
  const { config, response } = error
  if (response?.status === 401) {
    if (!isRefreshing) {
      isRefreshing = true
      try {
        const token = await refreshToken()
        requestQueue.forEach((cb) => cb(token))
        requestQueue = []
      } catch {
        redirectToLogin()
      } finally {
        isRefreshing = false
      }
    }

    return new Promise((resolve) => {
      requestQueue.push((token) => {
        config.headers.Authorization = 'Bearer ' + token
        resolve(axiosInstance(config))
      })
    })
  }
})

这是一个可用的最简版本,代码已经来到了 27 行,再加一些业务逻辑,这代码已经很丑了。

无队列无感刷新(promise 天然队列)

接下来看无队列的无感刷新,直接看代码:

TypeScript 复制代码
let refreshPromise: Promise<string> | null = null

async function tryRefreshToken(config) {
  if (!refreshPromise) {
    refreshPromise = axios.post('/api/v1/user/token/refresh')
      .catch(()=>{
        redirectToLogin()
      })
      .finally(() => { refreshPromise = null })
  }
  const token = await refreshPromise
  config.headers.Authorization = 'Bearer ' + token
  return axiosInstance(config)
}

哈哈,一共 14 行,实现的功能跟上面是一致的,当请求 401 时,你只需返回这个函数的结果即可,像这样

TypeScript 复制代码
if (error.response.status === 401) {
  return tryRefreshToken(config as AxiosRequestConfig)
}

在这个逻辑下,当多个请求几乎同时返回 401 时:

  • 只会触发一次刷新 token (由 !refreshPromise 控制);

  • 其他请求都会等待这次刷新完成,然后复用新 token 重新发起原请求;

  • 防止并发多次刷新 token (造成 race condition 竞态条件 和资源浪费)。

这就是所谓的:"Token 刷新单例 + 队列重试" 模式

只不过这个队列不用我们手动维护,而是使用了 promise 的机制形成的天然队列,请求会自动在 token 刷新后重新请求。

与常见的队列式方式对比一下

维度 隐式 Promise 方案 显式队列方案
复杂度 中高
语义清晰度 简洁但隐式 明确但冗长
调试难度 容易 稍高
实际效果 ✅ 等价 ✅ 等价

现在看看你项目里要不要换成这种简单高效的刷新方式?我已经用上了,结合我的业务代码也只有这么几行代码

TypeScript 复制代码
// handle 401
async function tryRefreshToken(config: AxiosRequestConfig) {
  if (!refreshPromise) {
    refreshPromise = axios
      .post('/api/v1/user/token/refresh')
      .then((res) => {
        if (res.status !== 200 || res.data.code !== 200) {
          redirectToLogin()
          throw new Error('Failed to refresh token')
        }
        const newToken = Cookies.get('access_token')!
        axiosInstance.defaults.headers.common['Authorization'] = 'Bearer ' + newToken
        return newToken
      })
      .catch((err) => {
        redirectToLogin()
        throw err
      })
      .finally(() => {
        refreshPromise = null
      })
  }

  const token = await refreshPromise
  config.headers!['Authorization'] = 'Bearer ' + token
  eventBus.emit('tokenRefreshed', token)
  return axiosInstance(config)
}

结语

我们常常为了便于思考用显式的结构去模拟隐式的行为,但是维护起来效果却适得其反,而在 Promise 时代,有时候"等待"本身就是最好的队列,感谢你花三分钟看完了我的文章,并学会了新的无队列无感刷新

恩师德莱厄斯(不是被迫的)

相关推荐
Highcharts.js8 小时前
线形比赛积分增长或竞赛图|Highcharts企业图表代码示列
开发语言·前端·javascript·折线图·highcharts·竞赛图
hpysirius8 小时前
在企业搭建一套完整的AI Agent系统
前端
追逐梦想永不停8 小时前
记录一个好用的excel判断数字格式的公式
前端·chrome·excel
hpysirius8 小时前
从零构建 Web 端视频剪辑器:技术实践与思考
前端
让学习成为一种生活方式8 小时前
大肠杆菌合成扑热息痛--对乙酰氨基酚--文献精读227
开发语言·前端·javascript
李白的天不白8 小时前
请求不到百度网址的原因
前端
多秋浮沉度华年8 小时前
electron 初始使用记录
javascript·arcgis·electron
Gary Studio8 小时前
Selinux编写
linux·服务器·前端
网络点点滴8 小时前
NPM的包版本管理
前端·npm·node.js
光影少年8 小时前
react性能优化比较好的办法有哪些?
前端·react.js·性能优化