Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Where should I Compose Complex Asynchronous Flows in Redux?

I want to model the following async logic using redux:

  1. User action triggers a chain of async API calls.
  2. Any API call might return 401 status (login timed out)
  3. If API responds with 401, display re-login popup
  4. On successful re-login, reissue API call and continue

I am not sure where to put this logic. Actions don't know about other actions, they only have access to dispatch, so they can't stop and wait for them to complete. Reducers don't have access to dispatch, so I can't put it there… so where does it live? Custom middleware? store.listen? In a smart component?

I'm currently using redux-promise-middleware & redux-thunk. How would one best organise this type of flow – without requiring buy-in into something like redux-saga or redux-rx, etc?

Also not sure best way to transparently interrupt the API call to perform those other actions i.e. API call shouldn't trigger its completed or failed actions until after the optional login process completes.

like image 226
timoxley Avatar asked Nov 09 '22 15:11

timoxley


1 Answers

It sounds to me like you'd want an action creator that generates a Thunk, and keep all that logic in the Thunk. There's really no other good way to preserve the association between your suite of API calls, and ensure that all the others are cancelled if one fails.

  1. In that Thunk, you'd fire your API calls, and collect their promises:

    const call1 = promiseGenerator1();
    const call2 = promiseGenerator2();
    const call3 = promiseGenerator3();
    const allCallPromises = [call1, call2, call3];
    
  2. Use an all() promise handler to monitor them:

    const watcher = Promise.all(allCallPromises).then(allSuccess, anyFail);
    
  3. Your fail handler will:

    • cancel the rest of the promises if any of them 401's. (Note, this requires a library like Bluebird that has cancellation semantics, or some other form of augmentation of your promise/request.)
    • dispatch an action or route-change to trigger the re-login window

      anyFail(error) => {
          if (error.status === 401) {
              allCallPromises.forEach((item)=> {item.cancel();});
              reLogin();
          }
      }
      
  4. Then, I'd be inclined to let your relogin component worry about re-firing that same complex action again, to issue all the calls.

  5. However, should your suite of API calls be somehow variable or context-specific, you could cache on the store the ones you need, from inside the anyFail handler. Have a reducer where you can stash an actionPendingReLogin. Compose an action that will re-fire the same calls as last time, and then dispatch it:

    dispatch(createAction('CACHE_RELOGIN_ACTION`, actionObjectToSaveForLater));
    

    (Or, just cache whatever action-creator you used.)

    Then, following successful relogin, you can:

    const action = store.getState('actionPendingReLogin');
    dispatch(action);
    // or:
    const actionCreator = store.getState('actionPendingReLogin');
    dispatch(actionCreator());
    

Oh: and in your allSuccess handler you'd simply dispatch the results of the async calls.

like image 75
XML Avatar answered Nov 14 '22 23:11

XML