Redux Saga嵌套的Generator函数效果无法使用yield puttypescript顺序运行

问题描述

我想执行两个异步操作,并且在两个都完成处理后执行第三个操作。为此,我创建了三个传奇工作者:第一个更新了DB上的email字段:

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,'data' | 'status'> = yield call(
      post,requestOpt
    );

    yield put(genericEmailSuccess({data,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运行,但它们直接要做的唯一事情就是调度动作。调度动作是同步的,因此该代码根本不会等待继续进行。

如果您想让两个Sagas完全保持当前状态,则可以使用take来监听那些Sagas最终将分派的动作,以暂停您的主传奇。例如:

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*/)将运行指定的传奇,直到完成。主传奇要返回或抛出后才能继续前进。