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

Node.js中令牌创建的并发处理:如何避免重复请求新Access Token

如何处理并发401时只调用一次Token刷新接口?

这是个非常典型的并发场景问题,很多开发者在处理Token过期逻辑时都会碰到。核心目标就是避免多个请求同时触发Token刷新,造成不必要的资源浪费甚至冲突。下面给你几个实用的解决方案:

1. 用互斥锁+请求队列控制并发

这是最通用的思路,不管你是在前端还是后端实现,核心逻辑都是:

  • 维护一个“正在刷新Token”的状态标记
  • 当第一个请求触发401时,检查标记:如果未刷新,则发起刷新请求并标记为“正在刷新”
  • 后续请求触发401时,看到标记为“正在刷新”,就加入等待队列,不重复发起刷新
  • 刷新完成后,更新全局Token,然后批量重试队列里的所有请求

举个前端的伪代码例子(用JavaScript):

// 全局状态:是否正在刷新Token,以及等待重试的请求队列
let isRefreshing = false;
let pendingRequests = [];

async function handleTokenExpiry(originalRequest) {
  // 如果已经有刷新请求在进行,加入队列等待
  if (isRefreshing) {
    return new Promise((resolve, reject) => {
      pendingRequests.push({ resolve, reject, request: originalRequest });
    });
  }

  isRefreshing = true;
  try {
    // 调用刷新Token接口
    const newToken = await fetch('/api/refresh-token').then(res => res.json());
    
    // 更新全局Token(比如存到localStorage或者请求头默认配置)
    localStorage.setItem('accessToken', newToken);
    
    // 重试所有排队的请求
    pendingRequests.forEach(({ resolve, request }) => {
      // 给原请求换上新Token
      request.headers.Authorization = `Bearer ${newToken}`;
      resolve(fetch(request));
    });
    pendingRequests = []; // 清空队列
    
    // 重试当前请求
    originalRequest.headers.Authorization = `Bearer ${newToken}`;
    return fetch(originalRequest);
  } catch (error) {
    // 刷新失败,处理错误(比如跳转到登录页)
    pendingRequests.forEach(({ reject }) => reject(error));
    pendingRequests = [];
    throw error;
  } finally {
    isRefreshing = false; // 重置刷新状态
  }
}

2. 借助请求库的拦截器统一处理

如果你的项目用了像Axios、Retrofit这类带拦截器的请求库,可以把这个逻辑封装到响应拦截器里,让所有请求自动处理401和Token刷新,不用每个请求单独写逻辑。

比如Axios的响应拦截器实现:

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

let isRefreshing = false;
let pendingRequests = [];

instance.interceptors.response.use(
  response => response,
  async error => {
    const originalRequest = error.config;
    
    // 只处理未重试过的401请求,避免无限循环
    if (error.response.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;
      
      if (isRefreshing) {
        // 加入等待队列
        return new Promise((resolve, reject) => {
          pendingRequests.push({ resolve, reject, req: originalRequest });
        }).then(newToken => {
          originalRequest.headers.Authorization = `Bearer ${newToken}`;
          return instance(originalRequest);
        }).catch(err => Promise.reject(err));
      }
      
      isRefreshing = true;
      try {
        const { data: newToken } = await instance.post('/refresh-token');
        instance.defaults.headers.common['Authorization'] = `Bearer ${newToken}`;
        
        // 重试队列请求
        pendingRequests.forEach(({ resolve, req }) => {
          req.headers.Authorization = `Bearer ${newToken}`;
          resolve(instance(req));
        });
        pendingRequests = [];
        
        return instance(originalRequest);
      } catch (refreshErr) {
        pendingRequests.forEach(({ reject }) => reject(refreshErr));
        pendingRequests = [];
        // 这里可以跳转到登录页或者清空Token
        window.location.href = '/login';
        return Promise.reject(refreshErr);
      } finally {
        isRefreshing = false;
      }
    }
    
    return Promise.reject(error);
  }
);

3. 后端辅助优化:Token宽限期

除了前端的并发控制,后端可以给Token设置一个“宽限期”——比如Token过期后5分钟内,旧Token仍然可以用于请求,同时后端在响应头里返回新的Token。这样客户端可以提前刷新Token,减少并发401的概率。不过这个是辅助手段,还是需要配合前面的并发控制逻辑,因为极端情况下还是可能出现多个401请求。

总结

最核心的就是用状态标记控制刷新请求的唯一性,用队列缓存等待的请求,确保同一时间只有一个Token刷新请求发出,其他请求复用刷新后的Token。这种方式不管是前端还是后端都适用,逻辑清晰且容易维护。

内容的提问来源于stack exchange,提问作者Omar El Malak

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 20:47:34