Redux: Was ist die Redux-Sprache für das Warten auf mehrere asynchrone Aufrufe?

Erstellt am 14. Sept. 2015  ·  30Kommentare  ·  Quelle: reduxjs/redux

Wenn ich eine Reaktionskomponente initiiere, muss ich zwei verschiedene asynchrone http-Aufrufe durchführen und auf deren Ergebnisse warten.
Bei diesen beiden Aufrufen werden die Daten idealerweise in zwei verschiedenen Reduzierungen gespeichert.

Ich verstehe das Muster für einen einzelnen asynchronen Anruf.
Ich sehe jedoch keine guten Beispiele für mehrere asynchrone Aufrufe, die parallel ausgeführt werden und darauf warten, dass beide Ergebnisse eintreffen.

Was ist der Redux-Weg, um dies zu tun?

question

Hilfreichster Kommentar

Ich gehe davon aus, dass Sie Redux Thunk Middleware verwenden.

Deklarieren Sie einige Aktionsersteller:

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

Beachten Sie, wie ich es mir zum Ziel gesetzt habe, ganz am Ende ein Promise indem ich die Kette als Rückgabewert der Funktion innerhalb der Thunk-Action-Ersteller behalte. Damit kann ich das machen:

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

Wenn Sie Redux Thunk verwenden, gibt dispatch den Rückgabewert der Funktion zurück, die Sie vom Thunk-Aktionsersteller zurückgegeben haben - in diesem Fall das Versprechen.

Auf diese Weise können Sie Kombinatoren wie Promise.all() , um auf den Abschluss mehrerer Aktionen zu warten:

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

Es ist jedoch vorzuziehen, einen anderen Aktionsersteller zu definieren, der als _composition_ der vorherigen Aktionsersteller fungiert, und intern Promise.all :

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

Jetzt kannst du einfach schreiben

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

Viel Spaß beim Versand!

Alle 30 Kommentare

Ich gehe davon aus, dass Sie Redux Thunk Middleware verwenden.

Deklarieren Sie einige Aktionsersteller:

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

Beachten Sie, wie ich es mir zum Ziel gesetzt habe, ganz am Ende ein Promise indem ich die Kette als Rückgabewert der Funktion innerhalb der Thunk-Action-Ersteller behalte. Damit kann ich das machen:

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

Wenn Sie Redux Thunk verwenden, gibt dispatch den Rückgabewert der Funktion zurück, die Sie vom Thunk-Aktionsersteller zurückgegeben haben - in diesem Fall das Versprechen.

Auf diese Weise können Sie Kombinatoren wie Promise.all() , um auf den Abschluss mehrerer Aktionen zu warten:

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

Es ist jedoch vorzuziehen, einen anderen Aktionsersteller zu definieren, der als _composition_ der vorherigen Aktionsersteller fungiert, und intern Promise.all :

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

Jetzt kannst du einfach schreiben

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

Viel Spaß beim Versand!

@gaearon Gibt es einen bestimmten Grund, warum Sie thunk gegenüber promise Middleware bevorzugen? oder Ratschläge, wann was zu verwenden ist? Vielen Dank.

Kein besonderer Grund, ich bin nur besser damit vertraut.
Probieren Sie beide aus und verwenden Sie das, was für Sie am besten funktioniert!

Verstanden, danke!

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

Wenn Sie möchten, können Sie einen Blick darauf werfen https://github.com/Cron-J/redux-async-transitions

@gaearon kurze Frage - Schauen Sie sich Ihr Beispiel oben an, wo Sie anrufen:

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

- Es sieht so aus, als würde der Block "Ich habe etwas getan" getroffen, selbst wenn doSomething abgelehnt würde - da Sie den Fehler in der doSomething -Kette nicht erneut auslösen, nachdem Sie ihn abgefangen haben. Ist das beabsichtigt? Wenn nicht - und wenn Sie den Fehler stattdessen dort erneut auslösen würden - würden Sie ihn im Anrufer erneut behandeln?

Es ist nur ein Beispiel, ich meine dies nicht als endgültige Referenz, nur um es zu betrachten. Ich habe keine Minute wirklich darüber nachgedacht.

Sie wissen, wie Sie Versprechen von Action-Machern zurückgeben können, der Rest liegt bei Ihnen. Es liegt ganz bei Ihnen, ob Sie Fehler erneut auslösen und wo Sie sie behandeln. Dies ist nicht wirklich ein Redux-Problem, daher ist mein Rat so gut wie der anderer, und in diesem Fall haben Sie viel mehr Kontext zu dem, was Sie bauen möchten und dem Stil, den Sie bevorzugen, als ich jemals haben werde.

Cool, danke! Ich dachte, das wäre der Fall, wollte aber sicherstellen, dass es keine Magie gab, die ich übersah.

Da Redux anpassbar ist, wird nicht garantiert, dass der Rückgabewert von dispatch als Versprechen erwartet wird.

Eigentlich bin ich im wirklichen Leben auf ein Problem gestoßen, seit ich Redux-Loop genommen habe. Es werden dispatch optimiert, um ein verpacktes Versprechen zurückzugeben (https://github.com/raisemarketplace/redux-loop/blob/master/modules/effects.js#L38). Wir können uns also nicht auf den Rückgabewert von dispatch .

Nun, wie Dan oben in diesem Thread gesagt hat: Es ist Ihre App, Sie schreiben Ihre Action-Ersteller, also können Sie entscheiden, was sie zurückgeben.

@markerikson Der Rückgabewert von dispatch ist nicht garantiert der Rückgabewert des Aktionserstellers, da Middleware ihn möglicherweise optimieren kann.

Aber ich habe eine Problemumgehung. Anstatt then auf dispatch(actonCreator()) verketten, können wir auch actionCreator() .

Ja, Middleware kann Dinge optimieren, aber mein Punkt ist, dass Sie die Middleware in Ihrer eigenen App einrichten :)

Hallo @gaearon , schnelle (hoffentlich verwandte) Frage! Wie stehen Sie dazu?

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

Dieses Code-Snippet enthält zwei verschachtelte asynchrone Aktionen. Sie funktionieren, aber ich habe Probleme beim Testen. Ich kann fetchDataA() testen, da es nur eine Ebene für asynchronen Aufruf gibt. Aber als ich versuchte, einen Test für fetchDataBWhichDependsOnA() zu schreiben, konnte ich die aktuellsten Werte nicht aus dem Block store in then abrufen.

@ Timothytong Sie geben die Versprechen nicht zurück. Wenn Sie das Ergebnis von dispatch und dem verschachtelten Aufruf von fetch , sollten Sie in Ihren Tests warten können, bis die gesamte Kette aufgelöst ist.

@johanneslumpe guter Fang, danke Kumpel! Jetzt bleibt nur noch die Frage, ob dies akzeptabel ist oder nicht, wenn es um die Datenabhängigkeit mit einer asynchronen Abrufkette geht.

Ich hatte das gleiche Problem und verbrachte 30 Minuten damit, eine Lösung zu finden. Ich bin mit dieser Implementierung noch nicht fertig, aber ich habe das Gefühl, dass sie auf dem richtigen Weg ist.

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

Meine Idee ist, dass eine Komponente das Auffüllen bestimmter Redux-Zustände anfordern kann. Wenn dies nicht der Fall ist, sollte auf eine Karte verwiesen werden, um die Aktionen zum Auffüllen aufzurufen und mit then . Für jede Aktion in der Kette müssen keine Daten übergeben werden. Sie kann nur die Daten aus dem Status übernehmen, da die abhängigen Aktionen sie ausgefüllt hätten.

@gaearon

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

Ich habe eine Frage dazu. Entschuldigung, wenn es so einfach ist.

In diesem Szenario gibt es zwei Versendungen. Das heißt, die Komponente wird zweimal gerendert. Was aber, wenn die Komponente möchte, dass beide Daten etwas Sinnvolles zeigen?

In diesem Szenario möchte ich, dass die Komponente nur einmal gerendert wird. In diesem Fall sind alle asynchronen Aktionen abgeschlossen.

Ich habe Namen von wenigen Middlewares erhalten, die dieses Problem zu behandeln scheinen, wie z. B. Redux-Batched-Subscribe- und Redux-Batched-Aktionen . Aber ein wenig verwirrt über den besten Ansatz.

Wenn dies ein gültiges Szenario ist, welcher Ansatz wird vorgeschlagen, um damit umzugehen?

@jintoppy Wenn Sie mehrere möchten, hilft Ihnen die Lösung Promise.all nicht weiter. weil beide doSomething() and doSomething() alle Versanddaten, die eine Statusänderung auslösen und das Rendern von Komponenten verursachen würden.

@jintoppy warum brauchst du mittelware, um mit deiner situation fertig zu werden?
Was hindert Sie daran, so etwas zu tun?

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

Sie können einfach alle Ihre Anforderungen in den einen Aktionsersteller stellen und die Aktion erst dann auslösen, wenn alle gelöst sind. In diesem Fall haben Sie den Reduzierer aufgerufen und daher den Status nur einmal geändert, sodass render () wahrscheinlich auch nur einmal ausgelöst wird.

@apranovich : Ich sehe ein Problem mit diesem Ansatz. Jetzt muss ich mehrere Abrufe in einer einzigen Aktion kombinieren. Wenn ich also 10 asynchrone Aufrufe habe, muss ich alle diese in einer einzigen Aktion zusammenfassen. Was ist auch, wenn ich später einen dieser 10 asynchronen Aufrufe auslösen möchte? Bei diesem Ansatz liegt im Grunde Ihre gesamte asynchrone Logik in einer einzigen Aktion.

Vielen Dank für die hilfreichen Informationen. Kurze Frage, wie das gleiche erreicht werden kann, aber stattdessen, wenn ein Abruf Daten zurückgibt, die für die nächste Abrufmethode erforderlich sind. Zum Beispiel gibt es einen kostenlosen Wetter-API- Endpunkt, an dem ich die Geolokalisierung eingebe, um eine ID (zuerst fetch ) zu erhalten, die für die nächsten fetch erforderlich ist, um das Wetter des Standorts zu erhalten. Ich weiß, dass dies ein seltsamer Anwendungsfall ist, aber ich würde gerne wissen, wie man mit ähnlichen Situationen umgeht. Danke im Voraus!

Hallo @gaearon , ich würde gerne Ihre Bewertung zu meiner Frage erhalten. Ich muss verschachtelte asynchrone Aufrufe an zwei APIs ausführen. Die Antwort der zweiten API hängt von der Antwort der ersten API ab.

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

}

Ich habe so etwas wie oben versucht, aber das funktioniert nicht. Und in meinem Projekt muss ich die Antwort von beiden APIs unabhängig voneinander verwenden.
Also, was ist der richtige Weg, es zu tun?

Vielen Dank.

@ c0b41 , bitte überprüfen Sie die aktualisierte Frage.

hey @ c0b41 , dies führt zu Syntaxfehlern beim zweiten Axios-Aufruf.

@ aayushis12 , @ c0b41 : Dies ist ein Bug-Tracker, kein Hilfe-Forum. Sie sollten versuchen, diese Frage bei Stack Overflow oder Reactiflux zu stellen. Es wird mehr Menschen geben, die die Frage sehen und helfen können.

Für diejenigen, die das erleben

Ich habe alles gemacht

Wird gedruckt, bevor alle asynchronen Aufrufe aufgelöst werden. Überprüfen Sie, ob Sie eine Anweisung oder einen Ausdruck von Ihren asynchronen Aktionserstellern zurückgeben.

Zum Beispiel, wenn doSomethingElse() in der Pfeilfunktion mit {} deklariert ist

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

"Ich habe alles getan" wird gedruckt, bevor doSomethingElse() . Lösen Sie dieses Problem, indem Sie {} nach => .

@gaearon eine Frage, basierend auf https://github.com/reduxjs/redux/issues/723#issuecomment -139927639 Wie würden Sie damit umgehen, wenn Sie eine Aktion auslösen müssen, die darauf wartet, dass zwei Aktionen _und_ den Status gesendet haben sich wegen dieser Handlungen verändert zu haben? Ich habe einen Fall, in dem die Aktionen ausgelöst werden, aber die Aktion innerhalb von then ausgeführt wird, bevor sich der Status geändert hat. Wir verwenden redux-thunk .

@enmanuelduran : Dan ist kein aktiver Betreuer mehr - bitte ping ihn nicht an.

Es ist schwer, Ihre Frage zu beantworten, ohne Code zu sehen, aber unsere Probleme sind wirklich nicht als Diskussionsforum gedacht. Es wäre besser, wenn Sie Ihre Frage auf Stack Overflow posten - dort erhalten Sie wahrscheinlich schneller eine bessere Antwort.

@markerikson oh, sorry, es war nicht meine Absicht, eine Frage in stackoverflow erstellt, wenn jemand interessiert ist: https://stackoverflow.com/questions/51846612/dispatch-an-action-after-other-specific-actions-were- Versand- und Zustandsänderung

vielen Dank, Jungs.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

ilearnio picture ilearnio  ·  3Kommentare

jbri7357 picture jbri7357  ·  3Kommentare

cloudfroster picture cloudfroster  ·  3Kommentare

amorphius picture amorphius  ·  3Kommentare

benoneal picture benoneal  ·  3Kommentare