使用带有 JWT 令牌的 Axios 对 Redux saga 产生调用的无限循环

Posted

技术标签:

【中文标题】使用带有 JWT 令牌的 Axios 对 Redux saga 产生调用的无限循环【英文标题】:Infinite loop on Redux saga yield call using Axios with JWT tokens 【发布时间】:2021-05-15 15:29:57 【问题描述】:

我一直在尝试通过 Redux-saga 使用 Redux-toolkit & react 使用 Axios 获取数据。似乎用令牌拦截 saga 调用会使 redux-saga 陷入无限循环?还是因为我的观察者?

我最近一直在学习如何编程,所以我在各个领域的技能都不是很好,希望你不要介意代码的编写方式,因为我一直在学习教程。

关于从 Header.tsxdispatch

的 handleSubmit
const handleSubmit = (e) => 
  e.preventDefault();
  dispatch(getCurrentUser());
;

我的 rootSaga.tsx 包括所有 watcherSagas 通知 getCurrentUser()

的调度
import  takeLatest  from "redux-saga/effects";
import 
  handleLogInUser,
  handleGetCurrentUser,
  handleSetCurrentUser,
 from "./handlers/user";
import 
  logInUser,
  getCurrentUser,
  setCurrentUser,
 from "../slices/user/userSlice";

export function* watcherSaga() 
  yield takeLatest(logInUser.type, handleLogInUser);
  yield takeLatest(getCurrentUser.type, handleGetCurrentUser);
  yield takeLatest(setCurrentUser.type, handleSetCurrentUser);

观察者为位于 user.tsx 文件 in handler 文件夹中的 saga 调用 handleGetCurrentUser

import  call, put  from "redux-saga/effects";
import  setCurrentUser  from "../../slices/user/userSlice";
import  requestLogInUser, requestGetCurrentUser  from "../requests/user";

export function* handleLogInUser(action) 
  try 
    console.log(action + "in handleLogInUser");
    yield call(requestLogInUser(action));
   catch (error) 
    console.log(error);
  


export function* handleGetCurrentUser(action) 
  try 
    const response = yield call(requestGetCurrentUser);
    const userData = response;
    yield put(setCurrentUser( ...userData ));
   catch (error) 
    console.log(error);
  

然后使用对 requestGetCurrentUser 的 yield 调用触发对以下 user.tsx in requests 文件夹的请求

import axiosInstance from "../../../axios/Axios";

export function requestGetCurrentUser() 
  return axiosInstance.request( method: "get", url: "/user/currentUser/" );

返回响应并放入 const userData,我 consoleLog()'d 处理程序并发现以下内容:

    它将成功到达处理程序 进入收益电话 获取数据成功 将数据返回给处理程序 然后它会再次重新启动整个 yield 调用?

它也永远不会返回给 userSlice 以放置数据。

axiosInstance 在我的 axios.tsx 文件中,该文件包含拦截器并获取 access_token 并将其添加到标头中。

import axios from "axios";

const baseURL = "http://127.0.0.1:8000/api/";

const axiosInstance = axios.create(
  baseURL: baseURL,
  timeout: 5000,
  headers: 
    Authorization: "Bearer " + localStorage.getItem("access_token"),
    "Content-Type": "application/json",
    accept: "application/json",
  ,
);

axiosInstance.interceptors.response.use(
  (response) => 
    return response;
  ,
  async function (error) 
    const originalRequest = error.config;

            if (typeof error.response === "undefined") 
      alert(
        "A server/network error occurred. " +
          "Looks like CORS might be the problem. " +
          "Sorry about this - we will get it fixed shortly."
      );
      return Promise.reject(error);
    

    if (
      error.response.status === 401 &&
      originalRequest.url === baseURL + "token/refresh/"
    ) 
      window.location.href = "/login/";
      return Promise.reject(error);
    

    if (
      error.response.data.code === "token_not_valid" &&
      error.response.status === 401 &&
      error.response.statusText === "Unauthorized"
    ) 
      const refreshToken = localStorage.getItem("refresh_token");

      if (refreshToken) 
        const tokenParts = JSON.parse(atob(refreshToken.split(".")[1]));

        // exp date in token is expressed in seconds, while now() returns milliseconds:
        const now = Math.ceil(Date.now() / 1000);
        console.log(tokenParts.exp);

        if (tokenParts.exp > now) 
          return axiosInstance
            .post("/token/refresh/", 
              refresh: refreshToken,
            )
            .then((response) => 
              localStorage.setItem("access_token", response.data.access);
              localStorage.setItem("refresh_token", response.data.refresh);

              axiosInstance.defaults.headers["Authorization"] =
                "JWT " + response.data.access;
              originalRequest.headers["Authorization"] =
                "JWT " + response.data.access;

              return axiosInstance(originalRequest);
            )
            .catch((err) => 
              console.log(err);
            );
         else 
          console.log("Refresh token is expired", tokenParts.exp, now);
          window.location.href = "/login/";
        
       else 
        console.log("Refresh token not available.");
        window.location.href = "/login/";
      
    

    // specific error handling done elsewhere
    return Promise.reject(error);
  
);

export default axiosInstance;

userSlice.tsx

import  createSlice  from "@reduxjs/toolkit";

const userSlice = createSlice(
  name: "user",
  initialState: ,
  reducers: 
    logInUser(state, action) ,
    getCurrentUser() ,
    setCurrentUser(state, action) 
      const userData = action.payload;
      console.log(userData + "we are now back in slice");
      return  ...state, ...userData ;
    ,
  ,
);

export const  logInUser, getCurrentUser, setCurrentUser  = userSlice.actions;

export default userSlice.reducer;

我发现如果我要删除授权令牌,它只会触发一次并退出无限循环,因为它会引发未经授权的错误。

任何建议将不胜感激,谢谢!

【问题讨论】:

您也可以发布您的操作创建者吗? 【参考方案1】:

抱歉这么晚才回来,我在不久前设法修复它纯粹是偶然的,我不完全明白为什么。

但我相信解决它的原因有以下两点:

更改调度操作的 useEffect 并确保处理程序返回 useEffect 期望更新的数据。

在处理程序中,我将userData解构为 userData ,我认为这意味着从axios请求返回的数据不是整个请求,而是实际返回的数据。

我的处理程序

    export function* handleGetCurrentUser() 
  try 
    console.log("in request get user");
    const response = yield call(requestGetCurrentUser);
    const  data  = response;
    yield put(setCurrentUser( ...data ));
   catch (error) 
    console.log(error);
  

我忘记将我的 useEffect 添加到创建操作的帖子中。

App.tsx 中的我的useEffect 将在应用第一次呈现时调度调用。然而,由于返回的数据没有更新预期的内容,它一直在重新渲染。

我不记得我的 useEffect 是什么,但目前它是以下内容:

我在 App.tsx 中的 useEffect

  const dispatch = useDispatch();

  useEffect(() => 
    dispatch(getCurrentUser());
  , [dispatch]);

  const user = useSelector((state) => state.user);

【讨论】:

以上是关于使用带有 JWT 令牌的 Axios 对 Redux saga 产生调用的无限循环的主要内容,如果未能解决你的问题,请参考以下文章

Axios JWT 不发送

如何从返回 axios 的响应对象访问 JWT 令牌值?

找不到 Slim jwt 令牌(由 axios 请求发送)

Axios - 防止在外部 api 调用上发送 JWT 令牌

如何将我的 JWT 令牌附加到每个 axios 调用?

Axios POST 调用不适用于 JWT 令牌,而 GET 调用有效