如何以正确的顺序从 Redux 中间件分派操作

Kor*_*sik 5 javascript dispatch-async redux redux-middleware

与世界上的应用程序一样,我的 React 应用程序需要对 API 执行一些 Ajax 调用。

我选择使用 Redux 中间件,以便正确地将 API 获取与我的组件分开。
这个想法是从我的组件中分派REQUEST操作。中间件监听它们并调度SUCCESSERROR操作:最后这些由减速器监听。

这里很多人已经问过如何从 Redux 中间件分派操作:这不是的问题的主题:)

首先,让我向您展示我用来编写的一个简单的减速器:

function currentUserReduxer(state = {}, action = {}) {
  const { currentUser, error } = action.payload;

  switch (action.type) {
    case 'GET_CURRENT_USER_REQUEST':
      return { ...state, isFetching: true, error: null };

    case 'GET_CURRENT_USER_SUCCESS':
      return { ...state, id: currentUser.id, isFetching: false };

    case 'GET_CURRENT_USER_FAILURE':
      return { ...state, isFetching: false, error };

    default:
      return state;
  }
}
Run Code Online (Sandbox Code Playgroud)

以及相应的中间件:

() => next => async (action) => {
  next(action);

  switch (action.type) {
    case'GET_CURRENT_USER_REQUEST': {
      try {
        const currentUser = await api.getCurrentUser();
        next(actions.getCurrentUserSuccess(currentUser));
      } catch (error) {
        next(actions.getCurrentUserFailure(error));
      }
      break;
    }

    default:
      break;
  }
};
Run Code Online (Sandbox Code Playgroud)

很长一段时间以来它都运行良好,然后我意识到它有部分错误:我的中间件没有返回 的值next,因此它破坏了中间件链,这是错误的!
由于next(action);这是我在中间件中执行的第一件事,所以我无法这么快返回它,因此我将其移到了中间件的末尾。我还决定分派新操作而不是使用next它们(毕竟,它们是新操作,将它们发送到整个中间件链确实有意义)。我的新中间件现在看起来像这样:

store => next => async (action) => {
  switch (action.type) {
    case'GET_CURRENT_USER_REQUEST': {
      try {
        const currentUser = await api.getCurrentUser();
        store.dispatch(actions.getCurrentUserSuccess(currentUser));
      } catch (error) {
        store.dispatch(actions.getCurrentUserFailure(error));
      }
      break;
    }

    default:
      break;
  }

  return next(action);
};
Run Code Online (Sandbox Code Playgroud)

它看起来很棒,但我现在有另一个问题:sincestore.dispatch是同步的,next(action)之后调用。这意味着我的减速器在or个REQUEST之后收到操作SUCCESSFAILURE:(

我认为一种解决方案可能是使用良好的旧承诺而不是await

store => next => async (action) => {
  switch (action.type) {
    case'GET_CURRENT_USER_REQUEST': {
      api.getCurrentUser()
        .then((currentUser) => {
          store.dispatch(actions.getCurrentUserSuccess(currentUser));
        })
        .catch((error) => {
          store.dispatch(actions.getCurrentUserFailure(error));
        });
      break;
    }

    default:
      break;
  }

  return next(action);
};
Run Code Online (Sandbox Code Playgroud)

另一个想法是store.dispatchsetTimeout

store => next => async (action) => {
  switch (action.type) {
    case'GET_CURRENT_USER_REQUEST': {
      try {
        const currentUser = await api.getCurrentUser();

        setTimeout(() => store.dispatch(actions.getCurrentUserSuccess(currentUser)));
      } catch (error) {
        setTimeout(() => store.dispatch(actions.getCurrentUserFailure(error)));
      }
      break;
    }

    default:
      break;
  }

  return next(action);
};
Run Code Online (Sandbox Code Playgroud)

说实话,我不太喜欢这两个解决方案,它们感觉太hacky了......

所以这是我的问题:我该如何处理我的问题?有没有更干净的方法来做到这一点?

提前致谢 :)

小智 2

看起来你想做的事情与Redux-Saga类似,我建议你看看他们的库。

取自他们的例子

// worker Saga: will be fired on USER_FETCH_REQUESTED actions
function* fetchUser(action) {
   try {
      const user = yield call(Api.fetchUser, action.payload.userId);
      yield put({type: "USER_FETCH_SUCCEEDED", user: user});
   } catch (e) {
      yield put({type: "USER_FETCH_FAILED", message: e.message});
   }
}

/*
  Starts fetchUser on each dispatched `USER_FETCH_REQUESTED` action.
  Allows concurrent fetches of user.
*/
function* mySaga() {
  yield takeEvery("USER_FETCH_REQUESTED", fetchUser);
}
Run Code Online (Sandbox Code Playgroud)