如何防止 redux-saga 调用我的 api 调用两次?

Posted

技术标签:

【中文标题】如何防止 redux-saga 调用我的 api 调用两次?【英文标题】:How to prevent redux-saga from calling my api call twice? 【发布时间】:2019-12-01 01:29:32 【问题描述】:

在新用户登录我的应用时,我正在使用 redux-saga 处理我的 API 调用。但是,我的 saga watcher 调用了我的 API 两次。第一次,API 正确获取数据,第二次,它给了我一个错误 400。

我尝试使用 takeLatest 传奇效果。没用。

我也尝试了这种自定义传奇效果。 import call, fork, take from "redux-saga/effects";

export function* takeOneAndBlock(pattern, worker, ...args) 
  const task = yield fork(function*() 
    while (true) 
      const action = yield take(pattern);
      yield call(worker, ...args, action);
    
  );
  return task;

它可以防止 API 渲染两次。但阻止我的会话和令牌管理的所有其他调用

这是我登录传奇的代码。

signInSaga.js

/* eslint-disable no-unused-vars */

import  put, call, takeEvery, delay  from "redux-saga/effects";


import 
  AUTHENTICATE_USER,
  CLEAR_ERRORS,
  SET_ERRORS,
  AUTH_INITIATE_LOGOUT,
  AUTH_CHECK_TIMEOUT
 from "../actions/types";
import * as actions from "../actions/signInActions";
//import qs library from es6 to stringify form data
import qs from "qs";
// import configured axios from "axios_api file";
import api from "../apis/axios_api";

/** function that returns data for token and session management */
function getToken() 
  //TODO: Add logic to get jwt token using username and password
  const expiresIn = 60 * 1000; // in milli seconds
  const expirationDate = new Date(new Date().getTime() + expiresIn);
  return 
    token: "fakeToken",
    userId: "fakeUserId",
    expirationDate,
    expiresIn
  ;


/** function that returns an axios call */
function loginApi(loginData) 
  //Axios doesn't stringify form data by default.
  //Hence, qs library is used to stringify upcoming redux-form data.

  return api.post("/login", qs.stringify(loginData));


/** saga worker that is responsible for the side effects */
function* loginEffectSaga(action) 
  try 
    // data that is obtained after axios call
    let history = action.history;

    let  data  = yield call(loginApi, action.payload);

    // dispatch authenticate user action to change redux state
    yield put(actions.authenticateUser(data, history));

    //to set and dispatch session and token management action creators
    const tokenResponse = getToken();
    yield localStorage.setItem("token", tokenResponse.token);
    yield localStorage.setItem("expirationDate", tokenResponse.expirationDate);
    yield localStorage.setItem("userId", tokenResponse.userId);

    yield put(actions.authSuccess(tokenResponse.token, tokenResponse.userId));
    yield put(actions.checkAuthTimeout(tokenResponse.expiresIn));

    //dispatch clear_errors action creator to remove any previous set errors
    yield put( type: CLEAR_ERRORS );

    // redirect to dashboard route after successful Login

    history.push("/dashboard");
   catch (e) 
    // catch error on a bad axios call and dispatch set_errors action creator
    yield put( type: SET_ERRORS, payload: e.response.data );
    console.log("errors", e.response);
  


/**
 * saga watcher that is triggered when dispatching action of type
 * 'AUTH_CHECK_TIMEOUT' and inturn it fires off AUTH_INITIATE_LOGOUT action creator
 */
function* checkAuthTimeout(action) 
  yield delay(action.expirationTime);
  yield put(actions.logout());


/**
 * saga watcher that is triggered when dispatching action of type
 * 'AUTH_INITIATE_LOGOUT'
 */
function* logout(action) 
  yield localStorage.removeItem("token");
  yield localStorage.removeItem("expirationDate");
  yield localStorage.removeItem("userId");
  yield put(actions.logoutSucceed());


/**
 * saga watcher that is triggered when dispatching action of type
 * 'SET_AUTHENTICATED'
 */
export function* loginWatcherSaga() 
  yield takeEvery(AUTHENTICATE_USER, loginEffectSaga);


export function* logoutWatcherSaga() 
  yield takeEvery(AUTH_INITIATE_LOGOUT, logout);


export function* checkAuthWatcherSaga() 
  yield takeEvery(AUTH_CHECK_TIMEOUT, checkAuthTimeout);

//rootSaga.js

import  all, fork  from "redux-saga/effects";
import * as signInSagas from "./signInSaga";
import * as signUpSagas from "./signUpSaga";
import * as forgotPasswordSagas from "./forgotPasswordSaga";

// import watchers from other files
export default function* rootSaga() 
  yield all(
    [
      ...Object.values(signInSagas),
      ...Object.values(signUpSagas),
      ...Object.values(forgotPasswordSagas)
    ].map(fork)
  );

//index.js

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const sagaMiddleware = createSagaMiddleware();

const store = createStore(
  rootReducer,
  composeEnhancers(applyMiddleware(sagaMiddleware))
);
sagaMiddleware.run(rootSaga);

【问题讨论】:

【参考方案1】:

我不认为你的传奇逻辑是错误的。也许调度动作的部分可能是错误的。

或者您可以像这样更改登录观察者,

export function* loginWatcherSaga() 
  while(true) 
    const authAction = yield take(AUTHENTICATE_USER);
    yield fork(loginEffectSaga, authAction);
    const action = yield take([AUTHENTICATE_SUCCESS, AUTHENTICATE_FAILURE]);
    // ... your logic

这样,第二个take 保证阻止其他操作类型,直到 `loginEffectSaga' 完成。

如果loginEffectSaga之后不需要其他逻辑,可以将fork改为call

【讨论】:

以上是关于如何防止 redux-saga 调用我的 api 调用两次?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 redux-saga 处理异步 API 调用?

多个 redux-sagas

redux-saga api yield 调用有效,但没有返回数据

redux-saga 与 redux 一起使用会导致 render() 被调用两次

redux-saga常用api概述

如何使用 redux-saga 在 API 响应中检查过期的 JWT 令牌?