wake-up-neo.com

Wie beendet man den Abruf vor dem Ausführen der nächsten Funktion in React?

Mit ReactJS habe ich zwei verschiedene API-Punkte, die ich versuchen zu bekommen und umzustrukturieren: students und scores. Sie sind beide ein Array von Objekten.

Mein Ziel ist : erstens Schüler und Ergebnisse bekommen, und zweitens, wenn Schüler und Ergebnisse im Zustand gespeichert sind, werde ich sie ändern und einen neuen Zustand erstellen, der auf dem Schüler- und Ergebniszustand basiert. Kurz gesagt, ich habe 3 Funktionen: getStudents, getScores und rearrangeStudentsAndScores. getStudents und getScores müssen beendet werden, bevor rearrangeStudentsAndScores ausgeführt werden kann.

Mein Problem ist : Manchmal wird rearrangeStudentsAndScores ausgeführt, bevor getScores abgeschlossen ist. Das hat rearrangeStudentsAndScores durcheinander gebracht. Aber manchmal würde es abgeschlossen sein. Ich weiß nicht, warum es 50% der Zeit funktioniert, aber ich muss es 100% der Zeit funktionieren lassen. 

Dies ist, was ich fetchstudents and scores in meiner Client-Datei habe:

function getStudents(cb){
    return fetch(`api/students`, {
        headers: {
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        }
    }).then((response) => response.json())
    .then(cb)
};

function getScores(cb){
    return fetch(`api/scores`, {
        headers: {
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        }
    }).then((response) => response.json())
    .then(cb)
};

Ich habe sie dann zusammengefügt:

function getStudentsAndScores(cbStudent, cbScores, cbStudentsScores){
    getStudents(cbStudent).then(getScores(cbScores)).then(cbStudentsScores);
}

In meiner Reaktions-App habe ich Folgendes:

getStudentsAndScores(){
    Client.getStudentsAndScores(
        (students) => {this.setState({students})},
        (scores) => {this.setState({scores})},
        this.rearrangeStudentsWithScores
    )
}

rearrangeStudentsWithScores(){
    console.log('hello rearrange!')
    console.log('students:')
    console.log(this.state.students);
    console.log('scores:');
    console.log(this.state.scores);        //this returns [] half of the time
    if (this.state.students.length > 0){
        const studentsScores = {};
        const students = this.state.students;
        const scores = this.state.scores;
        ...
    }
}

Irgendwie, bis ich zu rearrangeStudentsWithScores komme, wird this.state.scores noch [] sein. 

Wie kann ich sicherstellen, dass this.state.students und this.state.scores beide geladen sind, bevor ich rearrangeStudentsWithScores ausführen kann?

13
Iggy

Ihr Code mischt Fortsetzung Rückrufe und Versprechen. Sie finden es einfacher, zu begründen, dass Sie einen Ansatz für die asynchrone Flusskontrolle verwenden. Verwenden wir Versprechen, weil fetch sie verwendet.

// Refactor getStudents and getScores to return  Promise for their response bodies
function getStudents(){
  return fetch(`api/students`, {
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    }
  }).then((response) => response.json())
};

function getScores(){
  return fetch(`api/scores`, {
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    }
  }).then((response) => response.json())
};

// Request both students and scores in parallel and return a Promise for both values.
// `Promise.all` returns a new Promise that resolves when all of its arguments resolve.
function getStudentsAndScores(){
  return Promise.all([getStudents(), getScores()])
}

// When this Promise resolves, both values will be available.
getStudentsAndScores()
  .then(([students, scores]) => {
    // both have loaded!
    console.log(students, scores);
  })

Dieser Ansatz ist nicht nur einfacher, sondern auch effizienter, da beide Anforderungen gleichzeitig gestellt werden. Ihr Ansatz wartete, bis die Schüler abgeholt wurden, bevor sie die Noten abholten.

Siehe Promise.all in MDN

26
joews

Ich glaube, Sie müssen Ihre Funktionen in Pfeilfunktionen einschließen. Die Funktionen werden aufgerufen, während die Versprechenkette kompiliert und an die Ereignisschleife gesendet wird. Dies schafft eine Wettlaufsituation.

    function getStudentsAndScores(cbStudent, cbScores, cbStudentsScores){
  getStudents(cbStudent).then(() => getScores(cbScores)).then(cbStudentsScores);
}

Ich empfehle diesen Artikel für zusätzliche Lektüre: Wir haben ein Problem mit Versprechen von Nolan Lawson

Und hier ist ein Repo, das ich gemacht habe und ein Beispiel für jedes der Konzepte hat, von denen im Artikel die Rede ist . Pinky Swear

2

Ich würde die Umstrukturierung etwas empfehlen. Statt Ihren Status nach jedem Abrufaufruf zu aktualisieren, warten Sie, bis beide abgeschlossen sind, und aktualisieren Sie dann den Status auf einmal. Sie können dann die setStatecallback-Methode verwenden, um die nächste Methode auszuführen, die Sie möchten.

Sie können eine Promise-Bibliothek wie Bluebird verwenden, um zu warten, dass mehrere Abrufanforderungen abgeschlossen sind, bevor Sie etwas anderes tun

import Promise from 'bluebird'

getStudents = () => {
  return fetch(`api/students`, {
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    }
  }).then(response => response.json());
};

getScores = () => {
  return fetch(`api/scores`, {
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    }
  }).then(response => response.json());
};

Promise.join(getStudents(), getScores(), (students, scores) => {
    this.setState({
        students,
        scores
    }, this.rearrangeStudentsWithScores);
});
0
Anuj