Using ReactJS, I have two different API points that I am trying to get and restructure: students
and scores
. They are both an array of objects.
My goal is : first, get students and scores, and second, with students and scores saved in state, I will modify them and create a new state based on students and scores state. In short, I have 3 functions: getStudents
, getScores
, and rearrangeStudentsAndScores
. getStudents
and getScores
need to finish before rearrangeStudentsAndScores
can run.
My problem is: sometimes rearrangeStudentsAndScores
will run before getScores
would complete. That messed rearrangeStudentsAndScores
up. But sometimes it would complete. Not sure why it works 50% of the time, but I need to make it work 100% of the time.
This is what I have to fetch
students and scores
in my Client
file:
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)
};
I then combined them together:
function getStudentsAndScores(cbStudent, cbScores, cbStudentsScores){
getStudents(cbStudent).then(getScores(cbScores)).then(cbStudentsScores);
}
In my react app, I have the following:
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;
...
}
}
Somehow, by the time I get to rearrangeStudentsWithScores
, this.state.scores
will still be []
.
How can I ensure that this.state.students
and this.state.scores
are both loaded before I run rearrangeStudentsWithScores
?
Your code mixes continuation callbacks and Promises. You'll find it easier to reason about it you use one approach for async flow control. Let's use Promises, because fetch
uses them.
// 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);
})
As well as being simpler, this approach is more efficient because it makes both requests at the same time; your approach waited until the students were fetched before fetching the scores.