使用Redux-Toolkit AsyncThunk刷新Access Token遇重复执行问题求助
问题描述
我是Redux-Toolkit新手,当前在logoutUser asyncThunk中,当检测到返回错误码为401(Access Token过期)时,会调用refreshUserToken thunk获取新的刷新令牌,之后重试logoutUser,但这导致logoutThunk执行了两次。请问该如何修正这个问题?或者有没有更优的使用asyncThunks处理令牌刷新的方式?
注:我使用Cookie与服务器交互发送和接收Access Token及Refresh Token。
相关代码
User Slice代码
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; import axiosInstance from "../utils/axiosInstance"; export const logoutUser = createAsyncThunk( "users/logoutUser", async (_, { rejectWithValue, dispatch }) => { try { const response = await axiosInstance.post("/users/logout"); return response.data; } catch (error) { console.log(error.response.status); if (error.response.status === 401) { try { await dispatch(refreshUserToken()); const retryResponse = await dispatch(logoutUser()); return retryResponse; } catch (refreshError) { return rejectWithValue(refreshError.message); } } else { return rejectWithValue(error.message); } } } ); export const refreshUserToken = createAsyncThunk( "users/refreshUserToken", async (_, { rejectWithValue }) => { try { const response = await axiosInstance.post("/users/refresh-token"); console.log("refreshToken->", response.data); return response.data; } catch (error) { return rejectWithValue(error.message); } } ); const initialState = { user: {}, status: "idle", error: null, }; const userSlice = createSlice({ name: "user", initialState, reducers: {}, extraReducers(builder) { builder .addCase(logoutUser.pending, (state, action) => { state.status = "loading"; }) .addCase(logoutUser.fulfilled, (state, action) => { state.status = "success"; console.log("case f logout->", action.payload); // log statement state.user = {}; }) .addCase(logoutUser.rejected, (state, action) => { state.error = action.payload; }) .addCase(refreshUserToken.pending, (state, action) => { state.status = "loading"; }) .addCase(refreshUserToken.fulfilled, (state, action) => { state.status = "success"; console.log("case f refreshToken->", action.payload); // log statement }) .addCase(refreshUserToken.rejected, (state, action) => { state.error = action.payload; }); }, }); export const getUserState = (state) => state.user.status; export const getUserError = (state) => state.user.error; export const getUser = (state) => state.user.user; export const {} = userSlice.actions; export default userSlice.reducer;
Axios实例代码
const axiosInstance = axios.create({ baseURL: "http://localhost:8082/api", withCredentials: true });
解决方案
一、直接修正当前重复执行问题
你现在的核心问题是在logoutUser的catch逻辑中再次dispatch了logoutUser,导致该thunk被触发两次。正确的做法是刷新令牌后直接重新调用axios请求,而非再次dispatch thunk。
修改后的logoutUser代码:
export const logoutUser = createAsyncThunk( "users/logoutUser", async (_, { rejectWithValue, dispatch }) => { try { const response = await axiosInstance.post("/users/logout"); return response.data; } catch (error) { if (error.response.status === 401) { try { // 用unwrap()获取refreshUserToken的实际返回值,确保刷新成功 await dispatch(refreshUserToken()).unwrap(); // 直接调用axios请求,避免重复dispatch thunk const retryResponse = await axiosInstance.post("/users/logout"); return retryResponse.data; } catch (refreshError) { return rejectWithValue(refreshError.message); } } else { return rejectWithValue(error.message); } } } );
二、更优的全局令牌刷新方案
单个thunk中处理401会导致重复代码,更合理的方式是在axios拦截器中统一处理令牌刷新,让所有API请求自动处理401重试,无需在每个thunk中重复编写逻辑。
步骤1:改造Axios实例,添加响应拦截器
const axiosInstance = axios.create({ baseURL: "http://localhost:8082/api", withCredentials: true }); // 添加响应拦截器,统一处理401令牌过期 axiosInstance.interceptors.response.use( response => response, async error => { const originalRequest = error.config; // 仅处理401错误,且标记请求避免重复重试 if (error.response.status === 401 && !originalRequest._retry) { originalRequest._retry = true; try { // 发送刷新令牌请求 await axiosInstance.post("/users/refresh-token"); // 重试原请求 return axiosInstance(originalRequest); } catch (refreshError) { // 刷新失败(如Refresh Token也过期),可触发退出登录或跳转登录页逻辑 // dispatch(logoutUser()) return Promise.reject(refreshError); } } return Promise.reject(error); } ); export default axiosInstance;
步骤2:简化logoutUser thunk
现在无需在thunk中处理401逻辑,代码会更简洁:
export const logoutUser = createAsyncThunk( "users/logoutUser", async (_, { rejectWithValue }) => { try { const response = await axiosInstance.post("/users/logout"); return response.data; } catch (error) { return rejectWithValue(error.message); } } );
该方案优势
- 避免重复代码:所有API请求自动处理401重试,无需在每个asyncThunk中编写相同逻辑
- 逻辑集中:令牌刷新逻辑统一放在axios拦截器,维护更方便
- 避免thunk重复触发:直接重试axios请求,不会导致多次dispatch thunk的问题
内容的提问来源于stack exchange,提问作者Abinash
相关产品推荐
相关产品推荐

