You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

前端基于Axios实现Token刷新时拦截延迟API请求及多标签页场景的方案咨询

前端基于Axios实现Token刷新时拦截延迟API请求及多标签页场景的方案咨询

你这个需求太典型了!很多做前端鉴权的同学都会遇到——Token过期刷新时,不想让用户看到一堆错误提示,还要兼顾多标签页的场景,确实比一开始想的要复杂,但其实有成熟的方案,不管是手写实现还是用现成库都能搞定。

一、先解决单标签页的请求拦截与延迟问题

你想到的请求队列思路是完全正确的,但不用手动取消请求再重发,结合Axios的拦截器和Promise就能优雅实现,核心是用一个刷新状态标记和请求队列来控制:

  1. 定义全局状态:

    • 用变量isRefreshing标记是否正在刷新Token,初始为false
    • 用数组pendingRequests存储等待刷新完成后重新发起的请求(每个元素是Promise的resolve函数)
  2. 响应拦截器里处理Token过期:
    当收到401响应时,先判断是否为Token过期(可结合后端返回的错误标识,比如error.response.data.code === 'TOKEN_EXPIRED'):

    • 如果isRefreshing为false:
      • 将isRefreshing设为true,发起刷新Token的请求
      • 刷新成功后,更新本地的accessToken,遍历pendingRequests逐个调用resolve,重新发起队列中的请求
      • 刷新失败则跳转到登录页,清空队列
    • 如果isRefreshing为true:
      • 将当前请求包装成Promise,加入pendingRequests队列,等待刷新完成后再执行

给个简化的代码示例:

import axios from 'axios';

let isRefreshing = false;
let pendingRequests = [];

const api = axios.create({
  baseURL: '/api'
});

// 响应拦截器
api.interceptors.response.use(
  response => response,
  async error => {
    const originalRequest = error.config;
    // 排除刷新Token的请求本身,避免循环触发
    if (error.response.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;

      if (!isRefreshing) {
        isRefreshing = true;
        try {
          // 调用后端刷新Token接口
          const refreshResponse = await api.post('/auth/refresh', {
            refreshToken: localStorage.getItem('refreshToken')
          });
          const newAccessToken = refreshResponse.data.accessToken;
          // 更新本地Token与Axios默认请求头
          localStorage.setItem('accessToken', newAccessToken);
          api.defaults.headers.common['Authorization'] = `Bearer ${newAccessToken}`;

          // 重新发起队列中所有等待的请求
          pendingRequests.forEach(resolve => resolve(newAccessToken));
          pendingRequests = [];
        } catch (refreshError) {
          // 刷新失败,跳转登录页
          window.location.href = '/login';
          pendingRequests = [];
        } finally {
          isRefreshing = false;
        }
      }

      // 返回Promise,等待刷新完成后再发起原请求
      return new Promise(resolve => {
        pendingRequests.push(token => {
          originalRequest.headers['Authorization'] = `Bearer ${token}`;
          resolve(api(originalRequest));
        });
      });
    }
    return Promise.reject(error);
  }
);

二、多标签页场景的处理

你提到的BroadcastChannel确实是这个场景的最佳方案之一,它能实现同源不同标签页之间的通信:

  1. 创建BroadcastChannel实例:
const authChannel = new BroadcastChannel('auth_channel');
  1. 在刷新Token的逻辑中添加广播:
    当一个标签页开始刷新Token时,发送广播通知其他标签页“正在刷新Token”;刷新成功后,再发送“Token已更新”的广播,其他标签页收到后更新自身的Token即可。

  2. 其他标签页监听广播:

authChannel.addEventListener('message', (event) => {
  if (event.data.type === 'REFRESHING_TOKEN') {
    // 标记当前标签页处于等待状态,避免重复发起刷新请求
    isRefreshing = true;
  } else if (event.data.type === 'TOKEN_UPDATED') {
    // 更新本地Token与Axios请求头
    localStorage.setItem('accessToken', event.data.accessToken);
    api.defaults.headers.common['Authorization'] = `Bearer ${event.data.accessToken}`;
    isRefreshing = false;
    // 重新发起当前标签页中等待的请求
    pendingRequests.forEach(resolve => resolve(event.data.accessToken));
    pendingRequests = [];
  }
});

这样就能避免多个标签页同时发起刷新Token的请求,保证只有一个标签页执行刷新操作,其他标签页等待更新即可。

三、有没有现成的库可以直接用?

当然有!专门针对Axios的axios-auth-refresh库已经封装了请求队列、Token刷新的核心逻辑,你只需要配置刷新Token的函数就能快速落地,省去手写大量代码的麻烦。

用法示例:

import axios from 'axios';
import createAuthRefreshInterceptor from 'axios-auth-refresh';

const api = axios.create({ baseURL: '/api' });

// 定义刷新Token的逻辑函数
const refreshAuthLogic = async (failedRequest) => {
  const refreshToken = localStorage.getItem('refreshToken');
  const response = await axios.post('/auth/refresh', { refreshToken });
  const newAccessToken = response.data.accessToken;
  localStorage.setItem('accessToken', newAccessToken);
  failedRequest.response.config.headers['Authorization'] = `Bearer ${newAccessToken}`;
  return Promise.resolve();
};

// 给Axios实例添加刷新拦截器
createAuthRefreshInterceptor(api, refreshAuthLogic);

这个库已经处理了请求队列、避免重复刷新的问题,如果你需要多标签页支持,再结合上面的BroadcastChannel逻辑即可。

最后总结

其实你没把问题想复杂,这个场景本来就需要这些细节处理——核心就是「控制刷新状态」+「缓存等待请求」+「跨标签通信」。手写实现能灵活定制,用现成库能快速落地,完全可以根据你的项目需求选择合适的方案~

备注:内容来源于stack exchange,提问作者George

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.22 16:03:11