例如将 app-header.vue 的 created 钩⼦中调⽤多次 this.loadUserInfo(),设置 Token 失效并刷新⻚ ⾯,此时观察请求。 // layout/components/app-header.vue created () { // 第⼀次请求 this.loadUserInfo() // 第⼆次请求 this.loadUserInfo() }, 通过浏览器 Network ⾯板观察请求,发现有 2 次重复的刷新 Token 请求,由于 2 次刷新 Token 携带的 refresh_token 相同,会导致⼀次成功⼀次失败,⽽失败的⼀次会导致⻚⾯跳转登录⻚。 好⽐我去饮⽔机接⽔,我的朋友⼩明⽐我早到⼀步,但是发现饮⽔机的⽔桶空了,这时⼩明就将⼀桶新的⽔放进去。由于⼩明已经把⽔桶换过了,我就不⽤再去换⼀桶了,⽽是在他换完以后直接接⽔就可以了

    多个请求重复刷新 Token 处理 - 图1

    为了避免多次请求重复刷新 Token,可以通过⼀个变量 isRefreshing 标记 Token 的刷新状态

    默认状态为 false,并在发送刷新 Token 请求前检测,当状态为 false 才能发送。 发送刷新请求时,设置标记为 true 请求完毕,设置标记为 false // layout/components/app-header.vue // 是否正在更新 Token let isRefreshing = false request.interceptors.response.use(function (response) { } else if (status === 401) { if (!store.state.user) {} // 发送刷新请求前判断 isRefreshing 是否存在其他已发送的刷新请求 // 1 如果有,则将当前请求挂起,等到 Token 刷新完毕再重发,这⾥先设置为 return if (isRefreshing) { return } // 2. 如果没有,则更新 isRefreshing 并发送请求,继续执⾏后续操作 isRefreshing = true // 发送刷新请求 return request({ }).then(res => { }).catch(() => { }).finally(() => { // 3 请求完毕,⽆论成功失败,设置 isRefreshing 为 false isRefreshing = false }) } else if (status === 403) { 处理完毕,观察浏览器 Network ⾯板,refresh_token 请求减少为 1 次。

    多个请求重复刷新 Token 处理 - 图2

    虽然刷新 Token 的问题解决了,但之前发送的两个请求只有⼀个成功执⾏,其余的请求被阻⽌了。 (旧⽅式)我们需要将未成功触发 refresh_token 的请求从中⽌更改为挂起,可以通过 Promise 对象来实现。 挂起请求时创建⼀个 Promise 对象,将该 promise 的 resolve 统⼀保存。 当 Token 刷新完成,将统⼀保存的所有 resolve 调⽤。 我们声明⼀个数组存储所有被挂起的请求,当 Token 刷新完毕再将这些请求重新发送。 // layout/components/app-header.vue // 存储因等待 Token 刷新⽽挂起的请求 let requests = [] // 设置响应拦截器 request.interceptors.response.use(function (resp }, function (error) { if (isRefreshing) { // 将发送请求保存在函数中,存储到 requests 中等待执⾏,并 return 中⽌操作 return requests.push(() => { request(error.config) }) } isRefreshing = true return request({ }).then(res => { store.commit(‘setUser’, res.data.content // Token 刷新成功后,将 requests 中的请求重新发送 requests.forEach(callback => callback()) // 随后清空已被重新发送的请求 requests = [] return request(error.config) }).catch(() => { 浏览器测试后发现,刷新 Token 请求⼀次,其他请求也可正常发送。

    多个请求重复刷新 Token 处理 - 图3