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
});
我是 Redux-Toolkit 的新手,所以我很困惑。在
logoutUser
asyncThunk 中,我检查收到的错误代码是否为 401(访问令牌已过期),然后通过调用 refreshUserToken
thunk 内的 logoutUser
thunk 来请求刷新令牌,但是这导致 logoutThunk 运行两次。
我该如何纠正这个问题?或者是否有更好的方法来使用 asyncThunks 处理令牌刷新?
注意:我使用 cookie 来发送和接收来自服务器的访问和刷新令牌。
您可以重新调用
just端点,而不是重新分派
logoutUser
运行 Thunk 操作至少一次的操作。
示例:
export const logoutUser = createAsyncThunk(
"users/logoutUser",
async (_, { rejectWithValue, dispatch }) => {
try {
const { data } = await axiosInstance.post("/users/logout");
return data;
} catch(error) {
console.log(error.response.status);
if (error.response.status === 401) {
try {
// Attempt token refresh, unwrap result
await dispatch(refreshUserToken()).unwrap();
// Retry original request
const { data } = await axiosInstance.request(error.config);;
return data;
} catch(refreshError) {
return rejectWithValue(refreshError.message);
}
} else {
return rejectWithValue(error.message);
}
}
}
);
为了避免到处实现这种重试行为,您可以尝试使用响应拦截器来抽象和处理重试逻辑。
示例:
../utils/axiosInstance
import axios from "axios";
import { store } from "../path/to/store";
const axiosInstance = axios.create({
baseURL: "http://localhost:8082/api",
withCredentials: true
});
axiosInstance.interceptors.response.use(
response => response,
async error => {
if (error.response.status === 401) {
// Attempt token refresh, unwrap result
await store.dispatch(refreshUserToken()).unwrap();
// Retry original request
return axiosInstance.request(error.config);
}
return Promise.reject(error);
},
);
export default axiosInstance;
export const logoutUser = createAsyncThunk(
"users/logoutUser",
async (_, { rejectWithValue }) => {
try {
const { data } = await axiosInstance.post("/users/logout");
return data;
} catch(error) {
return rejectWithValue(error.message);
}
}
);