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

时间:2018-07-05 08:04:59

标签: javascript redux dispatch-async redux-middleware

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

我选择使用Redux中间件,以便正确地从组件中提取API。
这个想法是从我的组件中分发REQUEST个动作。中间件侦听它们并调度SUCCESSERROR动作:这些最后的动作由reducer监听。

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

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

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

以及相应的中间件:

() => 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;
  }
};

很长一段时间以来,它一直运行良好,然后我意识到部分错误:我的中间件没有返回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);
};

这看起来不错,但是我现在遇到另一个问题:由于store.dispatch是同步的,因此之后会调用next(action)这意味着我的减速器在REQUESTSUCCESS之后会收到FAILURE动作:(

我认为一种解决方案可能是使用良好的旧承诺而不是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);
};

另一个想法是将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);
};

说实话,我不太喜欢这两种解决方案,它们感觉很笨拙...

这是我的问题:我应该如何处理我的问题? 有更清洁的方法吗?

预先感谢:)

2 个答案:

答案 0 :(得分:3)

似乎您要尝试的操作类似于Redux-Saga,我建议您看一下它们的库。

根据他们的示例

fetch(your_url)
  .then(resp => resp.blob())
  .then(doSomethingWithBlob)

答案 1 :(得分:0)

我认为您的第一个解决方案已经接近答案。您可以引入术语sideEffects,它们是在分派操作时执行的异步函数。您的中间件功能仍在同步,操作将立即分派。这是一个示例:

getCurrentUserSideEffects = async (action) => {
    api.getCurrentUser()
        .then((currentUser) => {
          store.dispatch(actions.getCurrentUserSuccess(currentUser));
        })
        .catch((error) => {
          store.dispatch(actions.getCurrentUserFailure(error));
        });
}

<other side effects>

store => next => action => {
  switch (action.type) {
    case 'GET_CURRENT_USER_REQUEST': {
      getCurrentUserSideEffects(action);
      break;
    }

    default:
      break;
  }

  return next(action);
};

我认为这个想法与redux saga非常相似,但更简单易懂。