It's not the forEach
that is async (JavaScript, Node.js: is Array.forEach asynchronous?), it's the query.once("value")
. You can solve this problem in a variety of ways. With callbacks, promises, or await/async.
I suggest reading this very informative answer that covers much more with better examples: How do I return the response from an asynchronous call?
With callbacks
The idea here is to provide a function that can be called with the result once the asynchronous work is done, but after our work in forEach
is complete.
exports.getNearbyPosts = function (myLat, myLng, callback){
var query = firebase.database().ref("posts/").orderByKey();
var listOfItems = [];
query.once("value").then(function(snapshot) {
snapshot.forEach(function(childSnapshot) {
var key = childSnapshot.key;
var childData = childSnapshot.val();
var dist = getDistanceBetweenCoord(childData.lat, childData.lng, myLat, myLng);
if(dist < 10239820938502){
listOfItems.push(childSnapshot);
}
});
callback(listOfItems)
});
}
getNearbyPosts(0, 0, (result) => {
console.log(result);
});
With promises (ES2016)
Promises are another way to handle asynchronous code. I suggest reading about them here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises. It will explain them far better than I could.
Here is your code using promises:
exports.getNearbyPosts = function (myLat, myLng){
var query = firebase.database().ref("posts/").orderByKey();
var listOfItems = [];
return new Promise((resolve, reject) => {
query.once("value").then(function(snapshot) {
snapshot.forEach(function(childSnapshot) {
var key = childSnapshot.key;
var childData = childSnapshot.val();
var dist = getDistanceBetweenCoord(childData.lat, childData.lng, myLat, myLng);
if(dist < 10239820938502){
listOfItems.push(childSnapshot);
}
});
resolve(listOfItems);
});
});
}
getNearbyPosts(0, 0).then(result => {
console.log(result);
});
This can actually be simplified further as it appears query.once
returns a promise itself. We can simply return the promise provided to us by query.once
which will fire once after the .then
of your internal function. We can chain them together like so.
exports.getNearbyPosts = function (myLat, myLng){
var query = firebase.database().ref("posts/").orderByKey();
var listOfItems = [];
// We return the promise created by query.once
return query.once("value").then((snapshot) => {
// our inital .then will do some data filtering
snapshot.forEach((childSnapshot) => {
var key = childSnapshot.key;
var childData = childSnapshot.val();
var dist = getDistanceBetweenCoord(childData.lat, childData.lng, myLat, myLng);
if(dist < 10239820938502){
listOfItems.push(childSnapshot);
}
});
// We return here to continue the chain. The next .then that picks this up will have this result
return listOfItems;
});
}
// Our last promise resolution in the chain, containing listOfItems
getNearbyPosts(0, 0).then(result => {
console.log(result);
});