Firebase reads data asynchronously, so that the app isn't blocked while waiting for network traffic. Then once the data is loaded, it calls your callback function.
You can easily see this by placing a few log statements:
console.log("Before starting to load data");
ref.once("value", function(snapshot) {
console.log("Got data");
});
console.log("After starting to load data");
When you run this code the output is:
Before starting to load data
After starting to load data
Got data
This is probably not the order you expected, but it explains exactly why you get a zero length array when you log it. But since the main code continued on straight away, by the time you console.log(locations, locations.length)
the data hasn't loaded yet, and you haven't pushed it to the array yet.
The solution is to ensure all code that needs data from the data is either inside the callback, or is called from there.
So this will work:
var locations = []; // Empty array
var ref = db.ref("locations/");
ref.once("value", function(snapshot) {
snapshot.forEach(function(item) {
var itemVal = item.val();
locations.push(itemVal);
});
console.log(locations, locations.length);
});
As will this:
function loadLocations(callback) {
var locations = []; // Empty array
var ref = db.ref("locations/");
ref.once("value", function(snapshot) {
snapshot.forEach(function(item) {
var itemVal = item.val();
locations.push(itemVal);
});
callback(locations);
});
});
loadLocations(function(locations) {
console.log(locations.length);
});
A more modern variant of that last snippet is to return a promise, instead of passing in a callback.
function loadLocations() {
return new Promise(function(resolve, reject) {
var locations = []; // Empty array
var ref = db.ref("locations/");
ref.once("value", function(snapshot) {
snapshot.forEach(function(item) {
var itemVal = item.val();
locations.push(itemVal);
});
resolve(locations);
});
})
});
And you can then call it like this:
loadLocations().then(function(locations) {
console.log(locations.length);
});
Or with modern JavaScript you can use async
/ await
and do:
let locations = await loadLocations()
console.log(locations.length);
Just keep in mind that this last snippet still has the same asynchronous behavior, and the JavaScript runtime (or transpiler) is just hiding it from you.