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后重新执行队列中的请求。
错误原因分析
- Promise.resolve无返回值:Promise的
resolve函数执行后返回undefined,无法链式调用.then(),这是直接触发报错的核心原因。 - 队列存储信息不全:当前队列仅存储了Promise的
resolve/reject函数,未关联原始请求和对应的Redux action,导致后续无法正确处理请求结果的dispatch。 - 请求重发逻辑混乱:拦截器中尝试手动处理请求重发后的结果,和
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); }); }
关键修复说明
- 队列结构优化:新增
PendingRequest接口,明确存储原始请求、action、resolve/reject函数,解决信息缺失问题。 - resolve调用调整:Token刷新成功后直接调用
request.resolve(newToken),无需链式then,resolve仅负责唤醒addRequestToQueue中创建的Promise,后续逻辑由该Promise链处理。 - 逻辑职责统一:将请求重发和action dispatch的逻辑统一放到
addRequestToQueue中,避免拦截器逻辑混乱。 - action传递优化:在
requestWithAction中将action挂载到config上,确保拦截器能获取到对应的action并存入队列。 - 错误处理补充:新增Token刷新失败时的队列拒绝逻辑,避免请求无限pending。
内容的提问来源于stack exchange,提问作者Dolphin
相关产品推荐
相关产品推荐

