0

Trying to read (and expect) a nested array like this:

var array = [
0: {subArrayy: {...}, title: "Title"}, 
1: {subArray]: {...}, title: "Title"},
...

However after reading and (!) outputting, the result is fine. My web console shows me the array and everything seems good. BUT doing array.length returns 0. And any iteration returns undefined.

I've tried using ladosh _.toArray thing that I've seen earlier, but it does absolutely nothing.

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); // Adding new items seems to work at first

    });

  });
  console.log(locations, locations.length);

Output:

chrome output

I expected it to be iterable, in the way I could just use array0 to navigate.

Zackyy
  • 41
  • 5
  • Please show both the code that writes the database as well as the code that reads it. Right now we have to guess what the data actually looks like under "locations". – Doug Stevenson Apr 13 '19 at 21:49
  • Your console.log is being called before your async function `ref.once` has finished. Chrome will log the empty array initially, and then once the process has been complete fill in the missing parts except, apparently, stupidly, the data length. You should move your log inside the `ref.once`. – Andy Apr 13 '19 at 22:08
  • @DougStevenson I used this for my reference [link](https://github.com/PepsRyuu/spyfall/blob/master/locations.json) This IS the code that reads it. I've been having a lot of trouble figuring out how to use JSON with Javascript and decided to upload my code manually through Firebase console. – Zackyy Apr 13 '19 at 22:53
  • I'm asking you to also **edit the question** to show the code that writes the database, or at least describe the structure of the data that exists immediately under locations. Without seeing that, we don't know what exactly what the reading code is going to receive. – Doug Stevenson Apr 13 '19 at 22:56

3 Answers3

0

Placing a sleep function for about 300ms seems to fix the problem. I think it has something to do with syncing, though not entirely sure. It just needs some time to process the query and assign everything, I suppose.

Zackyy
  • 41
  • 5
  • Move the console.log() inside the callback function. It should return the correct length. Adding a setTimeout will throw incorrect results if the data download takes more than 300ms. – reflexgravity Apr 13 '19 at 23:15
0

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.

Community
  • 1
  • 1
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
0

Using await on read functions also seems to help.

Zackyy
  • 41
  • 5