Search code examples
javascriptreactjspromiseaxioses6-promise

setState from Promise values inside loop


I am using React with axios to make an external API call, loop through each object in the array passed back API call. Inside loop, I have a promise that has called another function that returns an object. I want to use the values from this object returned an assign them to a variable outside the loop that is an array used to set state but I can't seem to do so as it's always empty? Hopefully the comments in my code below help you understand my issue.

let self = this;
this.instance.get('/fixtures?timeFrame=n1').then((fixtures) => {
  // get all fixtures
  const allFixtures = fixtures.data.fixtures;
  // create valid fixtures array to add all fixture details to pass to fixtures state
  let validFixtures = [];
  // loop through all fixture objects in allFixtures array
  for (var i = 0; i < (allFixtures.length); i++) {
    // check if valid fixture, returns true or false
    let isValid = self.isValid(allFixtures[i]);
    // if fixture is valid
    if (isValid) {
      // get id of fixture to pass through to fixture route with id query
      let fixtureId = allFixtures[i]._links.self.href.split('v1/')
        .pop();
      // home teams name
      let homeTeam = allFixtures[i].homeTeamName;
      // away teams name
      let awayTeam = allFixtures[i].awayTeamName;
      // call head2head function to get all previous results from the two teams playing and returns average score
      // returns as object, example: { 'homeTeamAvgScore': 2, 'awayTeamAvgScore': 1 }
      self.getHead2Head(fixtureId, homeTeam,
        awayTeam).then((avg) => {
        //in here i want to push object into validFixtures array along with homeTeam and awayTeam as named values
        return validFixtures.push({
          'homeTeam': homeTeam,
          'awayTeam': awayTeam,
          'homeTeamAvgScore': avg.homeTeamAvgScore,
          'awayTeamAvgScore': avg.awayTeamAvgScore
        })
      });
    }
  }
  //validFixtures is empty??? 
  //How else can push to array and then later setState to fixtures with validFixtures array???
  self.setState({
    fixtures: validFixtures
  });
}).catch((error) => {
  console.log(error);
});
}

Solution

  • This specific requirement is known as a barrier. That is, you want to wait until n number of tasks have finished, and then do something. The "wait till n number of tasks finish" part can be achieved using the barrier.

    If you use Promises, this can be easily done using Promise.all. Axios exposes promise interfaces.

    If you don't want to use Promises, you either have to use something like the async npm library or implement a barrier by your own.

    UPDATE:

    Async - Await is not the same as Promise.all as mentioned in one of the other answers. The method suggested can degrade performance as the loop will run synchronously one after the other. This is explained clearly in the MDN docs.

    Example Fix,

    this.instance.get('/fixtures?timeFrame=n1')
        .then((fixtures) => {
            // ...Same code as yours
            const allFixtures = fixtures.data.fixtures;
            let promises = [];
    
            for (let i = 0; i < (allFixtures.length); i++) {
                // ... Same code as yours
                if (isValid) {
                    // ... Same code as yours
    
                    // Don't call "then". We will resolve these promises later
                    promises.push(this.getHead2Head(fixtureId, homeTeam, awayTeam));
                }
            }
    
            Promise.all(promises)
                .then(averages=>{
                    let validFixtures = averages.map((avg, index)=>{
                        return {
                            'homeTeam': allFixtures[index].homeTeamName,
                            'awayTeam': allFixtures[index].awayTeamName,
                            'homeTeamAvgScore': avg.homeTeamAvgScore,
                            'awayTeamAvgScore': avg.awayTeamAvgScore
                        };
                    });
                    this.setState({
                        fixtures: validFixtures
                    });
                });
        })
        .catch((error) => {
            console.log(error);
        });
    

    Few Side Notes:

    1. You don't need the self variable here. this scope is not changed as long as you use arrow functions(=>) instead of the function keyword.
    2. The way I've indented then resolves are a bit different. That is only because it seems more readable to me