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 时代,有时候"等待"本身就是最好的队列,感谢你花三分钟看完了我的文章,并学会了新的无队列无感刷新

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

相关推荐
养乐多同学943542 小时前
关于vuex的缓存持久实践
前端·vuex
Qinana2 小时前
🚙微信小程序实战解析:打造高质感汽车展示页
前端·css·程序员
ACGkaka_2 小时前
设计模式学习(十二)状态模式
学习·设计模式·状态模式
Yeats_Liao2 小时前
Go Web 编程快速入门 18 - 附录B:查询与扫描
开发语言·前端·后端·golang
@大迁世界2 小时前
第06章:Dynamic Components(动态组件)
前端·javascript·vue.js·前端框架·ecmascript
gustt3 小时前
用小程序搭建博客首页:从数据驱动到界面展示
android·前端·微信小程序
南蓝3 小时前
【javascript】什么是HMAC-SHA256 签名
前端
有点笨的蛋3 小时前
深入前端工程的细枝末节:那些被忽略却决定页面体验的 CSS 关键细节
前端·css
Holin_浩霖3 小时前
mini-react 动态渲染复杂的DOM结构
前端