Invoking epics from within other epics

2020-06-27 05:37发布

问题:

First of all many props for this awesome library! I am still struggling with an use case though. I would like to call another epic first and wait till it completes before continuing the current epic. Let's say I have a form where an user can change things. Before loading other data into the form, I want to save the current changes first. Any thoughts on this?

回答1:

Sounds like you want: if one epic would like to kick off another epics work and wait for it to complete before proceeding.

One approach is (using pseudo names), when receiving the initial action LOAD_OTHER_DATA you immediately start listening for a single SAVE_FORM_FULFILLED, which signals that the form has been saved (we'll kick it off in a bit). When received, we mergeMap (or switchMap, doesn't matter in this case) that into our call to load the other data loadOtherDataSomehow() and do the usual business. Finally, the trick to kick off the actual saving of the form we're waiting for is adding a startWith() at the end of that entire chain--which will emit and dispatch the action to actually save the form.

const saveFormEpic = (action$, store) =>
  action$
    .ofType('SAVE_FORM')
    .switchMap(() =>
      saveFormSomeHow(store.getState().formDataSomewhere)
        .map(details => ({
          type: 'SAVE_FORM_FULFILLED'
        }))
    );

const loadOtherDataEpic = action$ =>
  action$
    .ofType('LOAD_OTHER_DATA')
    .switchMap(() =>
      action$.ofType('SAVE_FORM_FULFILLED')
        .take(1) // don't listen forever! IMPORTANT!
        .mergeMap(() =>
          loadOtherDataSomeHow()
            .map(otherData => ({
              type: 'LOAD_OTHER_DATA_FULFILLED',
              payload: otherData
            }))
        )
        .startWith({
          type: 'SAVE_FORM'
        })
    );

You didn't mention how your loading and saving works, so this is just pseudo code that you'll need to amend for your use case.



回答2:

@jayphelps, I just wanted to say thank you for the all questions you have answered, they have really helped me solve many of my problems.

I was trying to formulate my question and found that you had already answered it here and managed to use your sample to fix my problem.

My example has/had 2 expics that I want/ed to call after each other:

const requestNameEpic = action$ =>
  action$.ofType("REQUEST_NAME")
    .switchMap(({id}) =>
      Observable.of(`name returned by 'ajax' call, using id: ${id}`) // Ajax simulation
        .delay(1000)
        .map(name => ({type: "LOAD_NAME", name}))
    );

const requestAgeEpic = action$ =>
  action$.ofType("REQUEST_AGE")
    .switchMap(({name}) =>
      Observable.of(`age returned by 'ajax' call, using name: [${name}]`)
        .delay(1000)
        .map(age => ({type: "LOAD_AGE", age}))
    );

The way I managed to solve it was using:

const requestDetailsEpic = action$ =>
  action$.ofType("LOAD_NAME")
    .map(({name}) => ({type: "REQUEST_AGE", name}));

So dispatching REQUEST_NAME would also dispatch REQUEST_AGE, but then I would never be able to only dispatch LOAD_NAME.

I refactored your sample to get:

const requestDetailsEpic = action$ =>
  action$.ofType("REQUEST_DETAILS")
    .switchMap(({id}) =>
      action$.ofType("LOAD_NAME")
        .take(1) // don't listen forever! IMPORTANT!
        .map(({name}) => ({type: "REQUEST_AGE", name}))
        .startWith({type: "REQUEST_NAME", id})
  );

Thank you again for all the great work you do on redux-observable and all the community support.