Redux: quel est l'idiome redux pour attendre plusieurs appels asynchrones?

Créé le 14 sept. 2015  ·  30Commentaires  ·  Source: reduxjs/redux

Lorsque je lance le composant react, je dois faire 2 appels http asynchrones différents et attendre leurs résultats.
Ces 2 appels placeront idéalement leurs données dans 2 réducteurs différents.

Je comprends le modèle pour faire un seul appel asynchrone.
Cependant, je ne vois pas de bons exemples pour faire plusieurs appels asynchrones, les faire s'exécuter en parallèle et attendre que _les deux_ leurs résultats arrivent.

Quelle est la manière redux de faire cela?

question

Commentaire le plus utile

Je suppose que vous utilisez le middleware Redux Thunk .

Déclarez certains créateurs d'action:

import 'babel-core/polyfill'; // so I can use Promises
import fetch from 'isomorphic-fetch'; // so I can use fetch()

function doSomething() {
  return dispatch => 
    fetch(
      '/api/something'
    ).then(
      response => response.json()
    ).then(
      json => dispatch({ type: DO_SOMETHING, json }),
      err => dispatch({ type: SOMETHING_FAILED, err })
    );
}

function doSomethingElse() {
  return dispatch => 
    fetch(
      '/api/something'
    ).then(
      response => response.json()
    ).then(
      json => dispatch({ type: DO_SOMETHING_ELSE, json }),
      err => dispatch({ type: SOMETHING_ELSE_FAILED, err })
    );
}

Notez comment je me fais un point d'honneur de renvoyer un Promise à la toute fin en gardant la chaîne comme valeur de retour de la fonction à l'intérieur des créateurs d'action thunk. Cela me permet de faire ça:

store.dispatch(doSomething()).then(() => {
  console.log('I did something');
});

Lorsque vous utilisez Redux Thunk, dispatch renvoie la valeur de retour de la fonction que vous avez renvoyée par le créateur d'action thunk - dans ce cas, la promesse.

Cela vous permet d'utiliser des combinateurs comme Promise.all() pour attendre la fin de plusieurs actions:

Promise.all([
  store.dispatch(doSomething()),
  store.dispatch(doSomethingElse())
]).then(() => {
  console.log('I did everything!');
});

Cependant, il est préférable de définir un autre créateur d'action qui agirait comme une _composition_ des créateurs d'action précédents, et d'utiliser Promise.all interne:

function doEverything() {
  return dispatch => Promise.all([
    dispatch(doSomething()),
    dispatch(doSomethingElse())
  ]);
}

Maintenant tu peux juste écrire

store.dispatch(doEverything()).then(() => {
  console.log('I did everything!');
});

Bonne expédition!

Tous les 30 commentaires

Je suppose que vous utilisez le middleware Redux Thunk .

Déclarez certains créateurs d'action:

import 'babel-core/polyfill'; // so I can use Promises
import fetch from 'isomorphic-fetch'; // so I can use fetch()

function doSomething() {
  return dispatch => 
    fetch(
      '/api/something'
    ).then(
      response => response.json()
    ).then(
      json => dispatch({ type: DO_SOMETHING, json }),
      err => dispatch({ type: SOMETHING_FAILED, err })
    );
}

function doSomethingElse() {
  return dispatch => 
    fetch(
      '/api/something'
    ).then(
      response => response.json()
    ).then(
      json => dispatch({ type: DO_SOMETHING_ELSE, json }),
      err => dispatch({ type: SOMETHING_ELSE_FAILED, err })
    );
}

Notez comment je me fais un point d'honneur de renvoyer un Promise à la toute fin en gardant la chaîne comme valeur de retour de la fonction à l'intérieur des créateurs d'action thunk. Cela me permet de faire ça:

store.dispatch(doSomething()).then(() => {
  console.log('I did something');
});

Lorsque vous utilisez Redux Thunk, dispatch renvoie la valeur de retour de la fonction que vous avez renvoyée par le créateur d'action thunk - dans ce cas, la promesse.

Cela vous permet d'utiliser des combinateurs comme Promise.all() pour attendre la fin de plusieurs actions:

Promise.all([
  store.dispatch(doSomething()),
  store.dispatch(doSomethingElse())
]).then(() => {
  console.log('I did everything!');
});

Cependant, il est préférable de définir un autre créateur d'action qui agirait comme une _composition_ des créateurs d'action précédents, et d'utiliser Promise.all interne:

function doEverything() {
  return dispatch => Promise.all([
    dispatch(doSomething()),
    dispatch(doSomethingElse())
  ]);
}

Maintenant tu peux juste écrire

store.dispatch(doEverything()).then(() => {
  console.log('I did everything!');
});

Bonne expédition!

@gaearon Y a-t-il une raison particulière pour laquelle vous préférez thunk au middleware promise ? ou des conseils sur quand utiliser quoi? Merci.

Aucune raison particulière, je la connais mieux.
Essayez les deux et utilisez ce qui vous convient le mieux!

Compris, merci!

JFYI :)
screen shot 2015-09-14 at 11 06 42 am

si vous le souhaitez, vous pouvez y jeter un œil https://github.com/Cron-J/redux-async-transitions

@gaearon question rapide - en regardant votre exemple ci-dessus, où vous appelez:

store.dispatch(doSomething()).then(() => {
  console.log('I did something');
});

- il semble que le bloc "J'ai fait quelque chose" serait touché même si doSomething rejeté - puisque vous ne relancez pas l'erreur dans la chaîne doSomething après l'avoir détectée. Est-ce intentionnel? Sinon - et si vous renverriez l'erreur à la place - la traiteriez-vous à nouveau dans l'appelant?

C'est juste un exemple, je ne veux pas dire que c'est une référence définitive, juste comme une façon de le regarder. Je n'y ai pas réfléchi une minute.

Vous savez comment rendre les promesses des créateurs d'action, le reste dépend de vous. Que ce soit pour renvoyer les erreurs, où les traiter, tout dépend de vous. Ce n'est pas vraiment un problème Redux, donc mon conseil est aussi bon que celui de quiconque, et dans ce cas, vous avez beaucoup plus de contexte sur ce que vous voulez construire et le style que vous préférez que je n'aurai jamais.

Cool merci! Je pensais que c'était le cas, mais je voulais m'assurer qu'il n'y avait pas de magie que j'ignorais.

Étant donné que Redux est personnalisable, la valeur de retour dispatch n'est pas garantie comme une promesse attendue.

En fait, j'ai rencontré un problème dans la vraie vie depuis que je prends redux-loop. Il modifie dispatch pour renvoyer une promesse encapsulée (https://github.com/raisemarketplace/redux-loop/blob/master/modules/effects.js#L38). Donc, nous ne pouvons pas dépendre de la valeur de retour de dispatch .

Eh bien, comme Dan l'a dit plus haut dans ce fil: c'est _votre_ application, _vous_ écrivez vos créateurs d'action, donc _vous_ pouvez décider de ce qu'ils retournent.

@markerikson La valeur de retour de dispatch n'est pas garantie d'être la valeur de retour du créateur d'action, car le middleware peut la modifier.

Mais j'ai une solution de contournement. Au lieu de chaîner then sur dispatch(actonCreator()) , nous pouvons enchaîner sur actionCreator() .

Oui, le middleware peut modifier les choses, mais mon point est que _vous_ configurez le middleware dans votre propre application :)

Salut @gaearon , question rapide (avec un peu de

function fetchDataBWhichDependsOnA() {
  return dispatch => {
    dispatch(fetchDataA())
      .then(() => {
        fetch('/api/get/data/B')
          .then(dispatch(receiveDataB(response.data)))
      })
  }
}

Il y a 2 actions asynchrones imbriquées dans cet extrait de code, elles fonctionnent mais j'ai du mal à le tester. Je suis capable de tester fetchDataA() car il n'a qu'un seul niveau d'appel asynchrone. Mais lorsque j'essayais d'écrire un test pour fetchDataBWhichDependsOnA() , je ne pouvais pas récupérer les valeurs les plus mises à jour du bloc store dans then .

@timothytong Vous ne dispatch et de l'appel imbriqué fetch vous devriez pouvoir attendre dans vos tests jusqu'à ce que toute la chaîne soit résolue.

@johanneslumpe bonne prise, merci

J'ai eu le même problème et j'ai passé 30 minutes à essayer de trouver une solution. Je n'ai pas fait cette implémentation, mais j'ai l'impression que c'est sur la bonne voie.

https://github.com/Sailias/sync-thunk

Mon idée est qu'un composant peut demander que certains états redux soient remplis, s'ils ne le sont pas, il devrait se référer à une carte pour appeler les actions pour les remplir et les enchaîner avec then . Chaque action de la chaîne n'a pas besoin de données transmises, elle peut simplement prendre les données de l'état car les actions dépendantes l'auraient remplie.

@gaearon

Promise.all([
  store.dispatch(doSomething()),
  store.dispatch(doSomethingElse())
]).then(() => {
  console.log('I did everything!');
});

J'ai une question à ce sujet. Désolé si c'est si basique.

Dans ce scénario, il y aura deux dépêches. Cela signifie que deux fois le composant sera rendu. Mais que se passe-t-il si le composant veut que les deux données montrent quelque chose de significatif?

Donc, dans ce scénario, je voudrais que le composant ne soit rendu qu'une seule fois et c'est à ce moment que toutes les actions asynchrones sont terminées.

J'ai des noms de quelques middlewares qui semblent gérer ce problème comme redux-batched-subscribe et redux-batched-actions . Mais, un peu confus sur la meilleure approche.

S'il s'agit d'un scénario valide, quelle serait l'approche suggérée pour gérer cela?

@jintoppy si votre demande est d'éviter les appels multiples pour rendre un composant, la solution Promise.all ne vous aidera pas; car les deux doSomething() and doSomething() distribuent toutes les données qui déclencheraient un changement d'état et provoqueraient le rendu du composant.

@jintoppy pourquoi avez-vous besoin d'intermédiaires pour gérer votre situation?
Qu'est-ce qui vous empêche de faire ça comme ça?

// action creator
function fetchAB() {
  return dispatch => {
    const fetchA = fetch( 'api/endpoint/A' );
    const fetchB = fetch( 'api/endpoint/B' );
    return Promise.all([ fetchA, fetchB ])
      .then( values => dispatch(showABAction(values)) )
      .catch( err => throw err );
  }
} 

Vous pouvez simplement placer toute votre demande dans le créateur d'action unique et lancer l'action une fois que toutes sont résolues. Dans ce cas, vous avez appelé le réducteur et l'état n'a donc changé qu'une seule fois, donc render () ne sera probablement déclenché qu'une seule fois.

@apranovich : Je vois un problème avec cette approche. Maintenant, je dois combiner plusieurs récupérations en une seule action. Donc, si j'ai, disons 10 appels asynchrones, je dois tous les mettre en une seule action. De plus, que faire si je souhaite déclencher un de ces 10 appels asynchrones plus tard? Dans cette approche, en gros, toute votre logique asynchrone résidera dans une seule action.

Merci à tous pour les informations utiles. Question rapide, comment peut-on réaliser la même chose, mais à la place, lorsqu'une extraction renvoie les données requises par la méthode de récupération suivante. Par exemple, il existe un point de terminaison API météo gratuit où je saisis la géolocalisation pour obtenir un identifiant (premier fetch ) qui est requis pour le prochain fetch pour obtenir la météo de l'emplacement. Je sais que c'est un cas d'utilisation étrange, mais j'aimerais savoir comment gérer des situations similaires. Merci d'avance!

Salut @gaearon , j'aimerais avoir votre avis sur cette question. Je dois faire des appels asynchrones imbriqués à deux API. La réponse de la deuxième API dépend de la réponse de la première.

export const getApi=()=>{
        return(d)=>{
        axios({
            method:'GET',
            url:Constants.URLConst+"/UserProfile",
            headers:Constants.headers
        }).then((response)=>{
            return d({
                type:GET_API_DATA,
                response,
                axios({
                    method:'GET',
                    url:Constants.URLConst+"/UserProfileImage?enterpriseId="+response.data.ProfileData.EnterpriseId,
                    headers:Constants.headers
                }).then((response1)=>{
                    return d({
                        type:GET_PROFILE_IMAGE,
                        response1
                    })
                })
            })
        }).catch((e)=>{
            console.log("e",e);
        })
    }

}

J'ai essayé quelque chose comme ci-dessus, mais cela ne fonctionne pas Et dans mon projet, je dois utiliser la réponse des deux API indépendamment.
Alors, quelle est la bonne façon de le faire?

Merci.

@ c0b41 , veuillez vérifier la question mise à jour.

hey @ c0b41 , cela donne des erreurs de syntaxe dans le deuxième appel axios.

@ aayushis12 , @ c0b41 : c'est un bug tracker, pas un forum d'aide. Vous devriez essayer de poser cette question sur Stack Overflow ou Reactiflux. Il y aura plus de gens qui verront la question et pourront aider.

Pour ceux qui vivent cela

j'ai tout fait

est imprimé avant que tous les appels asynchrones ne soient résolus, vérifiez si vous renvoyez une instruction ou une expression de vos créateurs d'actions asynchrones.

Par exemple, si doSomethingElse() est déclaré avec {} dans la fonction flèche

function doSomethingElse() {
  return dispatch => {
    fetch(
      '/api/something'
    ).then(
      response => response.json()
    ).then(
      json => dispatch({ type: DO_SOMETHING_ELSE, json }),
      err => dispatch({ type: SOMETHING_ELSE_FAILED, err })
    );
  }
}

"J'ai tout fait" s'affichera avant la résolution de doSomethingElse() . Résolvez ceci en déposant {} après => .

@gaearon une question, basée sur https://github.com/reduxjs/redux/issues/723#issuecomment -139927639 comment le géreriez-vous si vous devez envoyer une action qui attend que deux actions aient été distribuées _AND_ l'état avoir changé à cause de ces actions? J'ai un cas dans lequel les actions sont distribuées mais l'action à l'intérieur du then est exécutée _avant_ l'état a changé. Nous utilisons redux-thunk .

@enmanuelduran : Dan n'est plus un mainteneur actif - veuillez ne pas lui envoyer de ping.

Il est difficile de répondre à votre question sans voir le code, mais nos problèmes ne sont pas vraiment destinés à être un forum de discussion. Ce serait mieux si vous postez votre question sur Stack Overflow - vous y trouverez probablement une meilleure réponse, plus rapidement.

@markerikson oh, désolé, ce n'était pas mon intention, a créé une question dans stackoverflow si quelqu'un est intéressé: https://stackoverflow.com/questions/51846612/dispatch-an-action-after-other-specific-actions-were- expédié et changement d'état

merci beaucoup les gars.

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

timdorr picture timdorr  ·  3Commentaires

jimbolla picture jimbolla  ·  3Commentaires

benoneal picture benoneal  ·  3Commentaires

amorphius picture amorphius  ·  3Commentaires

CellOcean picture CellOcean  ·  3Commentaires