How to chain async actions and wait for the result

2019-06-09 14:25发布

问题:

I'm trying to write my INITIALIZE action which should chain some async actions together in the following way

  1. Call the initialize action.
  2. Call two async actions simultaneously.
  3. Wait for the completion of above actions.
  4. Run additional one action.
  5. Finish initialization.

here is the redux flow that I expect

INITIALIZATION_STARTED => ASYNC_ACTION_A_STARTED AND ASYNC_ACTION_B_STARTED => ASYNC_ACTION_A_FINISHED AND ASYNC_ACTION_B_FINISHED => ASYNC_ACTION_C_STARTED => ASYNC_ACTION_C_FINISHED => INITIALIZATION_FINISHED

I managed to achieve that flow using store.dispatch inside my epic, I know that this is anti-pattern and it will be removed in the 1.0.0 version so I would like to know how I can do it using pure epics

My working solution

export const initEpic = (action$: ActionsObservable<Action>, store) =>
  action$.filter(actions.initialization.started.match)
    .switchMap(action => (
      Observable.forkJoin(
        waitForActions(action$, actions.asyncA.done, actions.asyncB.done),
        Observable.of(
          store.dispatch(actions.asyncA.started(action.payload)),
          store.dispatch(actions.asyncB.started(action.payload)),
        )
      ).map(() => actions.asyncC.started(action.payload))
    )
  );

const waitForActions = (action$, ...reduxActions) => {
  const actionTypes = reduxActions.map(x => x.type);
  const obs = actionTypes.map(type => action$.ofType(type).take(1));
  return Observable.forkJoin(obs);
}

I have also been trying to use forkEpic from this comment like that

export const initEpic = (action$: ActionsObservable<Action>, store) =>
  action$.filter(actions.initialization.started.match)).mergeMap(action =>
    forkEpic(loadTagsEpic, store, actions.asyncA.started(action.payload))
      .concat(
        forkEpic(loadBranchesEpic, store, actions.asyncB.started(action.payload))
      )
      .map(() => actions.asyncC.started(action.payload))
  );

but it doesn't dispatch starting actions ASYNC_ACTION_A_STARTED and _ASYNC_ACTION_B_STARTED

回答1:

Sounds like merge is perfect for this. You'll start listening for asyncA.done and asyncB.done and then while waiting you'll kick off the requests by emitting asyncA.started and asyncB.started. These two streams are merged together as one, so it happens in the correct order and the actions emitted by either are emitted by our epic without needing store.dispatch.

const initEpic = action$ =>
  action$.filter(actions.initialization.started.match)
    .switchMap(action => (
      Observable.merge(
        waitForActions(action$, actions.asyncA.done, actions.asyncB.done)
          .map(() => actions.asyncC.started(action.payload)),
        Observable.of(
          actions.asyncA.started(action.payload),
          actions.asyncB.started(action.payload),
        )
      )
    )
  );

Here is a JSBin demoing: https://jsbin.com/yonohop/edit?js,console

It doesn't do any of the ASYNC_ACTION_C_FINISHED and INITIALIZATION_FINISHED stuff because code for that was not included in the question so not sure what it would have done.