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);
});
}
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:
self
variable here. this
scope is not changed as long as you use arrow functions(=>
) instead of the function
keyword.then
resolves are a bit different. That is only because it seems more readable to me