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

React中Axios刷新Token时出现Cannot read properties of undefined错误求助

修复Axios Token刷新逻辑中的Promise.resolve调用错误

问题背景

在React项目中使用axios@1.3.4实现Token自动刷新逻辑时,遇到caught (in promise) TypeError: Cannot read properties of undefined (reading 'then')错误,触发点为request.resolve(accessToken)行。该逻辑预期在access token过期时将请求存入队列,刷新新Token后重新执行队列中的请求。

错误原因分析

  1. Promise.resolve无返回值:Promise的resolve函数执行后返回undefined,无法链式调用.then(),这是直接触发报错的核心原因。
  2. 队列存储信息不全:当前队列仅存储了Promise的resolve/reject函数,未关联原始请求和对应的Redux action,导致后续无法正确处理请求结果的dispatch。
  3. 请求重发逻辑混乱:拦截器中尝试手动处理请求重发后的结果,和addRequestToQueue函数内的重发逻辑重复,导致流程冲突。

修复方案

核心调整点

  • 重构队列存储结构,将原始请求、action、Promise的resolve/reject函数一起存入队列,确保信息完整。
  • 让addRequestToQueue负责处理请求重发和结果dispatch,拦截器仅负责触发Token刷新和唤醒队列。
  • 移除拦截器中对request.resolve()返回值的.then()调用,改为直接resolve新Token,由之前创建的Promise链处理后续逻辑。

修复后的完整代码

import axios, { AxiosResponse, InternalAxiosRequestConfig } from 'axios';
import { v4 as uuid } from 'uuid';
import store from '../store/store';
import { ResponseCode, ResponseHandler, WheelGlobal } from 'js-wheel';

let isRefreshing = false;
// 定义队列存储结构,包含完整请求关联信息
interface PendingRequest {
  resolve: (token: string) => void;
  reject: (error: any) => void;
  originalRequest: InternalAxiosRequestConfig<any>;
  action?: (arg0: any) => any;
}
let pendingRequestsQueue: Array<PendingRequest> = [];

const instance = axios.create({
  timeout: 60000
});

instance.defaults.headers.post['Content-Type'] = 'application/json';

instance.interceptors.request.use((request) => {
  const accessToken = localStorage.getItem(WheelGlobal.ACCESS_TOKEN_NAME);
  if (accessToken) {
    request.headers['x-access-token'] = accessToken;
  }
  request.headers['x-request-id'] = uuid();
  return request;
},
(error: any) => {
  return Promise.reject(error);
});

function addRequestToQueue(originalRequest: InternalAxiosRequestConfig<any>, action?: (arg0: any) => any): Promise<any> {
  return new Promise((resolve, reject) => {
    // 存入完整的请求关联信息
    pendingRequestsQueue.push({
      resolve,
      reject,
      originalRequest,
      action
    });
  }).then((token: string) => {
    // 使用新Token更新请求头
    originalRequest.headers['x-access-token'] = token;
    originalRequest.headers['x-request-id'] = uuid();
    // 重发请求
    return instance(originalRequest);
  }).then((response: AxiosResponse<any, any>) => {
    // 若存在action,dispatch请求结果到Redux
    if (action) {
      const data = response.data.result;
      store.dispatch(action(data));
    }
    return response.data;
  }).catch((err) => {
    return Promise.reject(err);
  });
}

instance.interceptors.response.use((response: AxiosResponse<any, any>) => {
  const originalRequest: InternalAxiosRequestConfig<any> = response.config;
  
  // 正在刷新Token时,直接将请求加入队列
  if (isRefreshing) {
    addRequestToQueue(originalRequest, originalRequest.action);
    return response;
  }

  // 检测到access token过期
  if (response.data.resultCode === ResponseCode.ACCESS_TOKEN_EXPIRED) {
    // 将当前请求加入队列
    addRequestToQueue(originalRequest, originalRequest.action);
    isRefreshing = true;

    // 执行Token刷新操作
    ResponseHandler.handleWebCommonFailure(response.data)
      .then(() => {
        isRefreshing = false;
        const newToken = localStorage.getItem(WheelGlobal.ACCESS_TOKEN_NAME)!;
        // 唤醒队列中所有请求,传入新Token
        pendingRequestsQueue.forEach((request) => {
          request.resolve(newToken);
        });
        pendingRequestsQueue = [];
      })
      .catch((err) => {
        isRefreshing = false;
        // 刷新失败,拒绝所有队列中的请求
        pendingRequestsQueue.forEach((request) => {
          request.reject(err);
        });
        pendingRequestsQueue = [];
      });

    // 返回pending状态的Promise,等待队列处理完成
    return new Promise(() => {});
  }

  return response;
},
(error: any) => {
  return Promise.reject(error);
});

export function requestWithAction(config: any, action: (arg0: any) => any) {
  // 将action挂载到config上,方便拦截器获取
  config.action = action;
  return instance(config)
    .then((response: { data: { result: any; }; }) => {
      const data = response.data.result;
      store.dispatch(action(data));
      return response.data;
    })
    .catch((error: any) => {
      console.error(error);
      return Promise.reject(error);
    });
}

关键修复说明

  1. 队列结构优化:新增PendingRequest接口,明确存储原始请求、action、resolve/reject函数,解决信息缺失问题。
  2. resolve调用调整:Token刷新成功后直接调用request.resolve(newToken),无需链式then,resolve仅负责唤醒addRequestToQueue中创建的Promise,后续逻辑由该Promise链处理。
  3. 逻辑职责统一:将请求重发和action dispatch的逻辑统一放到addRequestToQueue中,避免拦截器逻辑混乱。
  4. action传递优化:在requestWithAction中将action挂载到config上,确保拦截器能获取到对应的action并存入队列。
  5. 错误处理补充:新增Token刷新失败时的队列拒绝逻辑,避免请求无限pending。

内容的提问来源于stack exchange,提问作者Dolphin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 21:37:36