具有多个 API 调用的 Redux saga 错误处理

Redux saga error handling with multiple API calls

我有两个 thunk,我正在尝试将其转换为一个 saga。当用户登录到他们的仪表板时,他们会进行两次 API 调用,一次用于项目,另一次用于通知。在 thunk 中,遵循 Flux 标准操作调用每个错误是微不足道的,但我不确定用 sagas 执行此操作的最佳方法是什么。这就是我要开始的:

export function* loadDashboard() {
  try {
    yield put({ type: types.notificationTypes.GET_NOTIFICATIONS_REQUEST });
    const [projects, notifications] = yield all([
      DashboardService.getProjects,
      DashboardService.getNotifications
    ]);
    yield put({
      type: types.projectTypes.GET_PROJECTS_SUCCESS,
      payload: projects
    });
    yield put({
      type: types.notificationTypes.GET_NOTIFICATIONS_SUCCESS,
      payload: notifications
    });
  } catch (error) {
    //My guess is both of these will render the same error to their reducer, 
    //regardless of who the originator is
    yield put({
      type: types.projectTypes.GET_PROJECTS_FAILURE,
      error: error.toString()
    });
    yield put({
      type: types.notificationTypes.GET_NOTIFICATIONS_FAILURE,
      error: error.toString()
    });
  }
}

我认为最好修复两个API请求的逻辑。

这提高了可读性和可维护性:

export function* loadDashboard() {
  yield all([loadProjects, loadNotifications])
}

function* loadProjects() {
  try {
    yield put({ type: types.GET_PROJECTS_REQUEST })
    const projects = yield call(DashboardService.getProjects)
    yield put({
      type: types.projectTypes.GET_PROJECTS_SUCCESS,
      payload: projects
    })
  } catch (error) {
    yield put({
      type: types.projectTypes.GET_PROJECTS_FAILURE,
      error: error.toString()
    })
  }
}

function* loadNotifications() {
  try {
    yield put({ type: types.notificationTypes.GET_NOTIFICATIONS_REQUEST });
    const notifications = yield call(DashboardService.getNotifications)
    yield put({
      type: types.notificationTypes.GET_NOTIFICATIONS_SUCCESS,
      payload: notifications
    })
  } catch (error) {
    yield put({
      type: types.notificationTypes.GET_NOTIFICATIONS_FAILURE,
      error: error.toString()
    })
  }
}

不过这段代码有点不同。

Redux-saga 的 all() is actually all-or-nothing:如果其中一个任务抛出错误,所有仍然 运行 的任务都会被取消。

我不认为这是你想要的,所以在我的代码中我通过捕获每个请求的错误来防止这种情况。

Task cancellation 和取消传播是 sagas 与 promises 的主要区别。这些东西通常默认工作,所以花点时间了解它们。

我最终将 all()spawn() 合并:

export function* loadDashboard() {
  yield all([
    spawn(projectActions.getProjects),
    spawn(notificationActions.getNotifications),
    spawn(teamActions.getTeams)
  ]);
}

From redux saga docs:

"分离叉(使用 spawn) 分离的分支存在于它们自己的执行上下文中。 parent 不会等待分离的叉子终止。来自生成任务的未捕获错误不会冒泡到 parent。并且取消 parent 不会自动取消分离的分叉(您需要明确取消它们)。

简而言之,分离分支的行为就像直接使用 middleware.run API 开始的根 Sagas。"

我通过从总共 3 个请求中的一个 GET 请求中排除 auth headers 来手动测试这个,并且错误单独传播,而其他人继续成功。我 运行 测试 SUCCESS 调用是否在失败后到达,只是为了确定。但是,当用 3 个 fork() 而不是 3 个 spawn() 调用编写相同的 loadDashboard() 方法时,该行为在功能上似乎是等效的。将需要编写适当的测试来确定哪种实现最适合这种情况。

编辑:在过去几个月 using/implementing saga 组合之后,我相信当你希望能够 cancel() child 进程和 spawn 是您不关心任务取消的时候。