An epic does not react to the actions of another epic

I have a problem with reduction observables. In my situation, one epic is waiting for the end of another epic. The second epic can make a request or return data from the cache. When the second one makes a request, everything works as expected, but when it returns the cache, the first one doesn't continue.

const { Observable } = Rx;

const FETCH_USER = 'FETCH_USER';
const FETCH_USER_FULFILLED = 'FETCH_USER_FULFILLED';
const FETCH_USER2 = 'FETCH_USER2';
const FETCH_USER_FULFILLED2 = 'FETCH_USER_FULFILLED2';
const FETCH_USER_REJECTED = 'FETCH_USER_REJECTED';
const FETCH_USER_CANCELLED = 'FETCH_USER_CANCELLED';

const fetchUser = id => ({ type: FETCH_USER, payload: id });
const fetchUserFulfilled = payload => ({ type: FETCH_USER_FULFILLED, payload });
const fetchUser2 = id => ({ type: FETCH_USER2, payload: id });
const fetchUserFulfilled2 = payload => ({ type: FETCH_USER_FULFILLED2, payload });
const cancelFetchUser = () => ({ type: FETCH_USER_CANCELLED });

let isFetchced = false;

const fakeAjax = url =>
  Observable.of({
    id: url.substring(url.lastIndexOf('/') + 1),
    firstName: 'Bilbo',
    lastName: 'Baggins'
  }).delay(1000);

const fakeAjax2 = url =>
  Observable.of({
    id: url.substring(url.lastIndexOf('/2') + 1),
    firstName: 'Bilbo2',
    lastName: 'Baggins2'
  }).delay(1000);

const fetchUserEpic = (action$, store) =>
  action$.ofType(FETCH_USER)
    .mergeMap(action => {
      const observable = isFetchced ? Observable.of({
        id: 2,
        firstName: 'Bilbo',
        lastName: 'Baggins'
      }) : fakeAjax(`/api/users/${action.payload}`);
      isFetchced = true;
      console.log(action);
      return observable
        .map(response => fetchUserFulfilled(response))
        .takeUntil(action$.ofType(FETCH_USER_CANCELLED))
    });

const fetchUserEpic2 = action$ =>
  action$.ofType(FETCH_USER2)
    .switchMap(() => action$.ofType(FETCH_USER_FULFILLED)
               .take(1)
    .mergeMap(() => {
        console.log("First epic");
        return fakeAjax2(`/api/users/${1}`)
            .map(response => fetchUserFulfilled2(response))
    }).startWith(fetchUser('redux-observable')));

const users = (state = {}, action) => {
  switch (action.type) {
    case FETCH_USER_FULFILLED:
      return {
        ...state,
        [action.payload.id]: action.payload
      };

    default:
      return state;
  }
};

const isFetchingUser = (state = false, action) => {
  switch (action.type) {
    case FETCH_USER:
      return true;

    case FETCH_USER_FULFILLED:
    case FETCH_USER_CANCELLED:
      return false;

    default:
      return state;
  }
};

      

Here is an emulation https://jsbin.com/qitutixuqu/1/edit?html,css,js,console,output . After clicking on the "Get user information" button in the console, you will see "First epic", after the second click on the button, there is no message on the console. If you add a delay to

Observable.of({
  id: 2,
  firstName: 'Bilbo',
  lastName: 'Baggins'
}).delay(10)

      

it starts up as expected.

+3


source to share


1 answer


Short answer: the first click is asynchronous, returning a delay of 1000ms in fetchUserEpic

. The second click represents a completely synchronous execution fetchUserEpic

, resulting in an internal actions$.ofType(FETCH_USER_FULFILLED)

missing action in fetchUserEpic2

.

Explanation:

Trace fetchUserEpic in the first click we get:

fetchUserEpic src: FETCH_USER2
fetchUserEpic2 src: FETCH_USER2
fetchUserEpic2 in: FETCH_USER2
fetchUserEpic2 out: FETCH_USER
fetchUserEpic src: FETCH_USER
fetchUserEpic in: FETCH_USER
fetchUserEpic2 src: FETCH_USER <- Notice location
fetchUserEpic out: FETCH_USER_FULFILLED
fetchUserEpic src: FETCH_USER_FULFILLED
fetchUserEpic2 src: FETCH_USER_FULFILLED
fetchUserEpic2-inner src: FETCH_USER_FULFILLED <- now subscribed
fetchUserEpic2-inner in: FETCH_USER_FULFILLED
First epic
fetchUserEpic2 out: FETCH_USER_FULFILLED2
fetchUserEpic src: FETCH_USER_FULFILLED2
fetchUserEpic2 src: FETCH_USER_FULFILLED2

      

Tracking the second time:



fetchUserEpic src: FETCH_USER2
fetchUserEpic2 src: FETCH_USER2
fetchUserEpic2 in: FETCH_USER2
fetchUserEpic2 out: FETCH_USER
fetchUserEpic src: FETCH_USER
fetchUserEpic in: FETCH_USER
fetchUserEpic out: FETCH_USER_FULFILLED
fetchUserEpic src: FETCH_USER_FULFILLED
fetchUserEpic2 src: FETCH_USER_FULFILLED
fetchUserEpic2 src: FETCH_USER <- Notice location

      

Since it fetchUserEpic2

subscribes to actions$

in the switchMap statement, it does not receive actions already dispatched. redux-observable uses regular Subject

and not ReplaySubject

or similar, so if the action is dispatched before the subscription, then the $ subscription actions will skip the action. For this reason, you need to be careful to ensure that actions are dispatched asynchronously when you depend on internal subscriptions such as fetchUserEpic2

.

Here is a modified source with instructions for logging the trace:

const fetchUserEpic = (action$, store) =>
  action$
    .do(a => console.log(`fetchUserEpic src: ${a.type}`))
    .ofType(FETCH_USER)
    .do(a => console.log(`fetchUserEpic in: ${a.type}`))
    .mergeMap(action => {
      const observable = isFetchced ? Observable.of({
        id: 2,
        firstName: 'Bilbo',
        lastName: 'Baggins'
      }) : fakeAjax(`/api/users/${action.payload}`);
      return observable
        .map(response => (isFetchced = true,fetchUserFulfilled(response)))
        .takeUntil(action$.ofType(FETCH_USER_CANCELLED))
    })
    .do(a => console.log(`fetchUserEpic out: ${a.type}`));

const fetchUserEpic2 = action$ =>
  action$
    .do(a => console.log(`fetchUserEpic2 src: ${a.type}`))
    .ofType(FETCH_USER2)
    .do(a => console.log(`fetchUserEpic2 in: ${a.type}`))
    .switchMap(() =>
      action$
        .do(a => console.log(`fetchUserEpic2-inner src: ${a.type}`))
        .ofType(FETCH_USER_FULFILLED)
        .do(a => console.log(`fetchUserEpic2-inner in: ${a.type}`))
        .take(1)
        .do(() => console.log("First epic"))
        .mergeMap(() =>
          fakeAjax2(`/api/users/${1}`)
            .map(response => fetchUserFulfilled2(response))
        ).startWith(fetchUser('redux-observable')))
    .do(a => console.log(`fetchUserEpic2 out: ${a.type}`));

      

+3


source







All Articles