web-dev-qa-db-fra.com

Comment finir tout récupérer avant d'exécuter la fonction suivante dans React?

En utilisant ReactJS, j’essaie d’obtenir et de restructurer deux points d’API différents: students et scores. Ils sont tous deux un tableau d'objets.

Mon objectif est : premièrement, obtenir les étudiants et les partitions, et deuxièmement, avec les étudiants et les partitions sauvegardées dans un état, je vais les modifier et créer un nouvel état basé sur les élèves et l’état des scores. En bref, j'ai 3 fonctions: getStudents, getScores et rearrangeStudentsAndScores. getStudents et getScores doivent être terminés avant que rearrangeStudentsAndScores puisse s'exécuter.

Mon problème est : parfois rearrangeStudentsAndScores s'exécutera avant que getScores ne se termine. Ce foutu rearrangeStudentsAndScores up. Mais parfois, cela se terminerait. Je ne sais pas pourquoi cela fonctionne 50% du temps, mais je dois le faire fonctionner à 100% du temps. 

Voici ce que je dois fetchstudents and scores dans mon fichier Client:

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

Je les ai ensuite combinés ensemble:

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

Dans mon application de réaction, j'ai les éléments suivants:

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

D'une manière ou d'une autre, au moment où j'arrive à rearrangeStudentsWithScores, this.state.scores sera toujours []

Comment puis-je m'assurer que this.state.students et this.state.scores sont tous deux chargés avant d'exécuter rearrangeStudentsWithScores?

13
Iggy

Votre code mélange rappel de continuation et Promises. Vous trouverez plus facile de raisonner à ce sujet si vous utilisez une approche pour le contrôle de flux async. Utilisons les promesses, parce que fetch les utilise.

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

En plus d'être plus simple, cette approche est plus efficace car elle effectue les deux demandes en même temps. votre approche a attendu que les étudiants aient été récupérés avant d'aller chercher les partitions.

Voir Promise.all sur MDN

26
joews

Je crois que vous devez envelopper vos fonctions dans les fonctions de flèche. Les fonctions sont appelées pendant la compilation de la chaîne de promesses et leur envoi à la boucle d'événements. Cela crée une condition de concurrence.

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

Je recommande cet article pour une lecture supplémentaire: Nous avons un problème avec les promesses de Nolan Lawson

Et voici un repo que j'ai fait qui a un exemple pour chacun des concepts mentionnés dans l'article . Pinky Swear

2
illuminatedSpace

Je recommanderais de restructurer légèrement - au lieu de mettre à jour votre état après chaque appel d'extraction, attendez que les deux soient terminés, puis mettez à jour l'état en même temps. vous pouvez ensuite utiliser la méthode setStatecallback pour exécuter la méthode suivante que vous souhaitez.

Vous pouvez utiliser une bibliothèque Promise telle que Bluebird pour attendre la fin de plusieurs demandes d’extraction avant de faire autre chose.

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