Search code examples
redux-saga

Is while true in a redux sage only for long polling or for other use cases?


I would normally guess redux-saga while(true) would be used for long-polling and I see other posts regarding long polling. However, I joined a project and I see around 44 sagas with while(true) which seems to imply there is no way it is for long polling on this project (I am guessing a typical project would be 1 or max 2 long polling to gather all info in one shot). Here is an example...

 function* enrollAddContactSaga() {
  while (true) {
    const { username, appName } = yield take(ENROLL_ADD_CONTACT);
    const newUserId = uuidv4();
    yield put(enrolllNewContact(newUserId, username));
    const { contactAddSuccess, contactAddFailure } = yield race({
      contactAddSuccess: take(ENROLL_NEW_CONTACT_ADD_SUCCESS),
      contactAddFailure: take(ENROLL_NEW_CONTACT_ADD_FAILURE),
    });
    if (contactAddSuccess) {
      yield put(enrollCheckCredentialStatus({ username, appName }));
    } else {
      yield put(
        showAlert('Oops!', buildErrorMessage(contactAddFailure), 'error'),
      );
    }
  }
}

Can someone shed some light on this? What is while(true) for?

I really would think the framework would constantly call this and the only break it would get is during a remote call and once that returns, the saga returns and since it is never done, it calls the method yet again.

NOTE: I also do not see any return statements in the generator saga functions to ever make it complete either.


Solution

  • Basically, while(true) with no break is useful when you want a saga to run for the lifetime of your app (or until cancelled by a parent saga). Usually this is because this saga is responsible for listening for some events, and those events never stop being relevant.

    In your specific code, the saga enters the loop, then immediately yields until an ENROLL_ADD_CONTACT action happens. The saga doesn't need to poll for this to happen, redux saga will be checking any actions that are dispatched and will wake your saga if and when it's necessary. If the action never happens, then your saga will remain paused forever. Assuming the action does happen though, your saga resumes execution at that time.

    As the rest of the saga runs, it is going to do some other asynchronous things, and during that time it's not listening for more ENROLL_ADD_CONTACT actions. So hypothetically, if one of those actions happens during this time, it will be ignored. Eventually, the end of the loop is reached and it loops back to the top, at which point it starts listening for the action again.

    In other words, the code you showed is implementing something very similar to takeLeading, just in a single saga instead of split into two. So a similar behavior could be achieved like this:

    function* enrollAddContactSaga() {
      yield takeLeading(ENROLL_ADD_CONTACT, someOtherSaga);
    }
    
    function* someOtherSaga(action) {
      const { username, appName } = action;
      const newUserId = uuidv4();
      yield put(enrolllNewContact(newUserId, username));
      const { contactAddSuccess, contactAddFailure } = yield race({
        contactAddSuccess: take(ENROLL_NEW_CONTACT_ADD_SUCCESS),
        contactAddFailure: take(ENROLL_NEW_CONTACT_ADD_FAILURE),
      });
      if (contactAddSuccess) {
        yield put(enrollCheckCredentialStatus({ username, appName }));
      } else {
        yield put(
          showAlert('Oops!', buildErrorMessage(contactAddFailure), 'error'),
        );
      }
    }