2017-07-15 3 views
3

J'ai un problème avec redux-observables. Dans ma situation, une épopée attend la fin d'une autre épopée. La deuxième épopée peut faire une demande ou retourner des données à partir du cache. Lorsque second fait que la demande fonctionne comme prévu, mais quand il retourne le cache le premier ne continue pas.Epic ne réagit pas à l'action d'une autre épopée

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; 
    } 
}; 

Voici l'émulation https://jsbin.com/qitutixuqu/1/edit?html,css,js,console,output. Après avoir cliqué sur le bouton "Récupérer infos utilisateur" dans la console, vous pouvez voir "Première épopée", après le deuxième clic sur le bouton il n'y a pas de message dans la console. Si vous ajoutez le délai à

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

il commence à fonctionner comme prévu.

+0

Pour votre information, j'ai essayé d'aider, mais ne pouvait pas comprendre ce qui est attendu et exactement ce que le problème est. Trop de pièces mobiles. Le jsbin est également différent des exemples fournis, ce qui ajoute une confusion supplémentaire. – jayphelps

Répondre

3

Réponse courte: Le premier clic est asynchrone en renvoyant un délai de 1000 ms en fetchUserEpic. Le second clic est une exécution entièrement synchrone de fetchUserEpic, ce qui entraîne la suppression de l'action actions$.ofType(FETCH_USER_FULFILLED) dans fetchUserEpic2.

Explication:

Tracing fetchUserEpic dans le premier clic, nous obtenons ceci:

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 

Tracing la deuxième fois que nous obtenons:

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 

Depuis fetchUserEpic2 est abonnée à actions$ dans le switchMap déclaration, il ne reçoit pas les actions qui ont déjà été distribuées. redux-observable utilise un Subject normal, pas un ReplaySubject ou similaire, donc si l'action est distribuée avant l'abonnement, les actions $ abonnement manqueront l'action. Pour cette raison, vous devez veiller à ce que les actions soient distribuées de manière asynchrone lorsque vous utilisez des abonnements internes comme fetchUserEpic2.

est ici la source modifiée avec les états d'enregistrement de traçage:

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}`));