Reputation: 3938
I am trying to make a Firebase database call inside of a loop and an outer firebase call. The inner Firebase database call is using data returned from the outer firebase call and the loop, which is why it is run within the outer one. The results then should be set into the state.
Problem The value that is being retrieved in the inner Firebase database call is not being set in the state.
Theory Since Firebase database calls are asynchronous, my guess is that the inner Firebase database call does not finish before the loop completes and sets the state. Therefore, I created a promise for the inner Firebase database call, so that the loop would wait for the call to finish before moving onto the next item. However, the value retrieved is still not being set.
Does anyone know why the loop does not wait for promise containing call to Firebase database?
MY ATTEMPT
userRef.on("value", function(snapshot) {
var snap = [];
// loop through each branch received from firebase
snapshot.forEach(function(data) {
var firstThingsFirst = data.val().firstThingsFirst;
var someID = data.val().someID;
var myPromise = new Promise(function(resolve, reject) {
userRef.child('somechild').child(someID).once('value').then(function(newSnapshot) {
console.log("newSnapshot = (below)");
console.log(newSnapshot.val());
resolve(newSnapshot.val());
}, function(error) {
// Something went wrong.
console.error("error (below)");
console.error(error);
reject("noValueFound")
});
});
var someValue = "";
myPromise.then(function(valueRetrieved) {
console.log(".then of promise is running...");
console.log("valueRetrieved = (below)");
console.log(valueRetrieved);
someValue = this.checkUndefined(valueRetrieved);
}.bind(this));
var array = {"firstThingsFirst": firstThingsFirst, "someValue": someValue};
snap.push(array);
});
this.setState({
snapshots: snap
});
}.bind(this));
Alternative Attempt:
userRef.on("value", function(snapshot) {
var snap = [];
// loop through each branch received from firebase
snapshot.forEach(function(data) {
var firstThingsFirst = data.val().firstThingsFirst;
var someID = data.val().someID;
var someValue = this.fetchValueByID(someID);
var array = {"firstThingsFirst": firstThingsFirst, "someValue": someValue};
snap.push(array);
});
this.setState({
snapshots: snap
});
}.bind(this));
fetchValueByID(someID) {
userProfileRef.child('someChild').child(someID).once('value').then(function(snapshot) {
console.log("snapshot (fetchValueByID) = (below)");
console.log(snapshot.val());
return snapshot.val();
})
}
I have also tried the approaches recommended by Firebase:
Thanks in advance.
Upvotes: 1
Views: 1272
Reputation: 6112
Does anyone know why the loop does not wait for promise containing call to Firebase database?
The reason for this is that you require setState
to be called after all the fetches are done. But, your code doesn't do anything to wait. You just carry on with the loop and once it's done, call setState
. You never really know whether your fetches completed or not. You need a way to wait for all the the fetches. In a nutshell, there is a problem because of mixing of synchronous and asynchronous code.
You can try this. The idea is to map all the fetchValueByID
(I've added a return
in the beginning) calls into an array of promises and then wait for all of them to resolve (using Promise.all
) before doing setState
userRef.on("value", function(snapshot) {
// loop through each branch received from firebase
// AND map to array of promises
var promises = [];
snapshot.forEach(function(data) {
var firstThingsFirst = data.val().firstThingsFirst;
var someID = data.val().someID;
promises.push(this.fetchValueByID(someID).then(function(someValue) {
return {
"firstThingsFirst": firstThingsFirst,
"someValue": someValue
};
}));
});
// Wait for all promises to resolve
Promise.all(promises).then(function(results) {
this.setState({
snapshots: results
});
}.bind(this))
}.bind(this));
fetchValueByID(someID) {
// Notice the return here
return userProfileRef.child('someChild').child(someID).once('value').then(function(snapshot) {
console.log("snapshot (fetchValueByID) = (below)");
console.log(snapshot.val());
return snapshot.val();
})
}
I've faked all the possible data and converted my solution to a simple-to-understand snippet below
var promises = [];
// Faking the snapshot
[{
a: 1,
b: 10
}, {
a: 2,
b: 20
}].forEach(function(data) {
var firstThingsFirst = data.a
var someID = data.b
promises.push(fetchValueByID(someID).then(function(someValue) {
return {
"firstThingsFirst": firstThingsFirst,
"someValue": someValue
};
}));
});
// Wait for all promises to resolve
Promise.all(promises).then(function(results) {
console.log(results);
});
function fetchValueByID(someID) {
// Dummy Promise resolution
// Notice the return here
return new Promise(function(resolve, reject) {
setTimeout(function() {
// Dummy manipulation
resolve(someID * 100);
});
}).then(function(snapshot) {
console.log("snapshot (fetchValueByID) = (below)");
console.log(snapshot);
return snapshot;
})
}
Upvotes: 3