问题
I'm trying to write my INITIALIZE
action which should chain some async actions together in the following way
- Call the initialize action.
- Call two async actions simultaneously.
- Wait for the completion of above actions.
- Run additional one action.
- 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. 😁
You might notice this is mostly a regular RxJS question where the items streaming happen to be actions. This is really helpful because when you ask for help you can ask from the entire RxJS community if you craft the question as generic RxJS.
Note that I listened for done before starting; this is generally a best practice in case done
is emitted synchronously after started
. If you didn't listen first, you'd miss it. Since it's async it doesn't matter, but still generally a best practice and helpful when you unit test.
来源:https://stackoverflow.com/questions/47000384/how-to-chain-async-actions-and-wait-for-the-result-without-store-dispatch