Redux Saga 嵌套生成器函数效果不会使用 yield put(打字稿)按顺序运行

Redux Saga nested Generator function effects don't runs in sequence using yield put (typescript)

我想执行两个异步操作,当两个操作都完成处理后,再执行第三个操作。为此,我创建了三个 saga worker:第一个更新 DB 上的电子邮件字段:

export function* emailUpdateRequestSaga(action: IEmailUpdateRequest) {
  const requestURL = '/updateEmail';
  const requestData = {
    userId: action.userId,
    email: action.email
  };
  try {
    const {data, status}: Pick<AxiosResponse, 'data' | 'status'> = yield call(
      update,
      requestURL,
      requestData
    );

    yield put(emailUpdateSuccess({data, status}));
  } catch (err) {
    console.log('err', err);
    yield put(emailUpdateFail(err));
  }
}

第二个发送邮件:

export function* genericEmailRequestSaga(action: IGenericEmailRequest) {
  const requestURL = '/sendEmail';

  const requestOpt = {
    headers: {},
    body: {
      email: action.email
    }
  };

  try {
    const {data, status}: Pick<AxiosResponse, 'data' | 'status'> = yield call(
      post,
      requestURL,
      requestOpt
    );

    yield put(genericEmailSuccess({data, status}));
  } catch (err) {
    console.log('err', err);
    yield put(genericEmailFail(err));
  }
}

然后,第三个包起来放成功动作:

export function* emailSendAndUpdateRequestSaga(action: IEmailSendAndUpdateRequest) {
  try {
    // first generator
    yield put(emailUpdateRequest(action.userId, action.email));

    // second generator
    yield put(genericEmailRequest(action.email));

    // success action
    yield put(emailSendAndUpdateSuccess(true));

  } catch (err) {
    console.log('err', err);
    yield put(emailSendAndUpdateFail(err));
  }
}

这是观察者:

export function* sagas() {
  yield takeEvery(EmailActionEnum.SEND_EMAIL_REQUEST, genericEmailRequestSaga);
  yield takeEvery(EmailActionEnum.EMAIL_UPDATE_REQUEST, emailUpdateRequestSaga);
  yield takeEvery(EmailActionEnum.EMAIL_SEND_AND_UPDATE_REQUEST, emailSendAndUpdateRequestSaga);
}

问题是,在 emailSendAndUpdateRequestSaga 中,yield put(emailSendAndUpdateSuccess(true)); 在之前的请求启动后被触发,但即使它们失败了,我仍然会触发成功操作。

我只想在前面的生成器无误地完成处理后才触发第三个动作,我该怎么做?

// first generator
yield put(emailUpdateRequest(action.userId, action.email));

// second generator
yield put(genericEmailRequest(action.email));

虽然这些行确实间接导致其他 sagas 发生 运行,但它们直接做的唯一事情就是派发一个动作。分派一个动作是同步的,所以这段代码在继续之前根本不会等待。

如果您想完全按照当前的方式保留这两个 saga,那么您可以使用 take 来监听这些 saga 最终将派发的操作,以便暂停您的主要 saga。例如:

export function* emailSendAndUpdateRequestSaga(action: IEmailSendAndUpdateRequest) {
  try {
    yield put(emailUpdateRequest(action.userId, action.email));
    const action = yield take([
      // I'm guessing the action types are constants something like this, but they weren't in your sample code.
      EmailActionEnum.EMAIL_UPDATE_SUCCESS, 
      EmailActionEnum.EMAIL_UPDATE_FAIL
    ]);
    if (action.type === EmailActionEnum.EMAIL_UPDATE_FAIL) {
      throw action;
    }

    yield put(genericEmailRequest(action.email));
    const action = yield take([
      EmailActionEnum.SEND_EMAIL_SUCCESS, 
      EmailActionEnum.SEND_EMAIL_FAIL
    ]);

    if (action.type === EmailActionEnum.SEND_EMAIL_FAIL) {
      throw action;
    }

    // success action
    yield put(emailSendAndUpdateSuccess(true));

  } catch (err) {
    console.log('err', err);
    yield put(emailSendAndUpdateFail(err));
  }
}

我认为更好的解决方案是改变您的方法,而不是分派操作,而是直接调用 sagas。结合修改 sagas 以便它们在出现错误时抛出,您可以执行以下操作:

export function* emailUpdateRequestSaga(action: IEmailUpdateRequest) {
  const requestURL = '/updateEmail';
  const requestData = {
    userId: action.userId,
    email: action.email
  };
  try {
    const {data, status}: Pick<AxiosResponse, 'data' | 'status'> = yield call(
      update,
      requestURL,
      requestData
    );

    yield put(emailUpdateSuccess({data, status}));
  } catch (err) {
    yield put(emailUpdateFail(err));
    throw err; // <---- added this to rethrow the error
  }
}

export function* genericEmailRequestSaga(action: IGenericEmailRequest) {
 // ... code omitted. Add a throw like in emailUpdateRequestSaga
}

export function* emailSendAndUpdateRequestSaga(action: IEmailSendAndUpdateRequest) {
  try {
    // Changed to `call` instead of `put`
    yield call(emailUpdateRequestSaga, emailUpdateRequest(action.userId, action.email));

    // Changed to `call` instead of `put`
    yield call(genericEmailRequestSaga, genericEmailRequest(action.email));

    // success action
    yield put(emailSendAndUpdateSuccess(true));

  } catch (err) {
    console.log('err', err);
    yield put(emailSendAndUpdateFail(err));
  }
}

yield call(/*etc*/) 将 运行 指定的 saga 直到它完成。主要传奇在 returns 或抛出之前不会继续。