使用带有 JWT 令牌的 Axios 对 Redux saga yield 调用进行无限循环

Infinite loop on Redux saga yield call using Axios with JWT tokens

我一直在尝试通过 Redux-saga 使用 Axios 使用 Redux-toolkit 获取数据并做出反应。似乎在无限循环中拦截带有令牌的 saga 调用会得到 redux-saga?还是因为我的关注者?

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

On handleSubmit 从 Header.tsxdispatch

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

my 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);
}

watcher 调用 handleGetCurrentUser 用于 handler 文件夹中 user.tsx 文件中的 saga:

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 调用,在 requests 文件夹

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

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

响应被返回并放入 const userData,我对处理程序进行了 consoleLog() 处理并发现了以下内容:

  1. 它将成功到达处理程序
  2. 转到 yield 调用
  3. 获取数据成功
  4. return 返回处理程序的数据
  5. 然后它再次重新启动整个 yield 调用?

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

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

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;

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

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

很抱歉这么晚才回来,我刚刚偶然修复了它,但我不太明白为什么。

但我相信解决它的是以下两件事:

更改调度操作的 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 添加到创建动作的 post。

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

我记不清我的 useEffect 是什么了,但目前是这样的:

我在 App.tsx

中的 useEffect
  const dispatch = useDispatch();

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

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