Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

JavaScript

Firebase promises

Hello all!

I am using firebase as a backend and querying it to populate a list in React Native. The data structure follows the firefeed example (https://firefeed.io/about.html), where all of the posts are stored in a global node in firebase and each user stores a reference key to their profile. Flow is like this:

  • user creates post and saves it to global posts node
  • a reference key is stored to user's profile
  • a reference key is stored to feed node organized by ID for all of the users followers.

The challenge i have is that I am trying to render the list, but it's coming back as a "seemingly" empty array. When I console.log it out, the array is an empty bracket and the length of the array is 0. However, when I click the arrow, I see the desired data.

I believe this has to do with order of execution.

The code is:

listen() {
    var self = this
    // get current user ID
    var currentUserID = firebase.auth().currentUser.uid
   // query user class to get reference keys to posts
    var postList = firebase.database().ref('/users/'+currentUserID+'/postKeys')
   //create posts array to store posts that are queried
 var posts = []
  // query the post class
    postList.once('value').then(function(snapshot) {
      snapshot.forEach((child)=> {
        // get postID
        var postID = child.val().postID
        // append postID to get location of post data
        var postRef = firebase.database().ref('posts/'+postID)
        // get post data
        postRef.once('value').then(function(postSnap) {
           // save data to variable post
          post = postSnap.val()
          push post object to array
          posts.push(post)
         // setState here returns desired value, but probably bad practice because I set the state as many times as there are posts.
        })
      }) //.then here returns error
    }) // .then here returns empty array
  }

Any help with this would be greatly appreciated. Thank you

Joseph Dalton
Joseph Dalton
12,489 Points

Hey Aaron,

First, I think your console.log() confusion is just coming from the promises not yet being fulfilled when console.logging posts. I am guessing that when you first log the posts array, the calls to the database are still pending (the postRef promises), however the posts array has already been created, so it initially gets logged as empty. But with objects and arrays, the console usually logs a reference to the object, not a snapshot of it, so as the object or array gets updated in your logic, it will eventually be reflected in the console's log (I think until you expand it). So while it looks empty at first, by the time you expand it, the promises have been fulfilled and the values are present.

To see a snapshot of the array in the console, you could make a copy of it when logging it (via calling arr.slice(0)), or by using JSON.stringify(arr) to print a string.

So, this leads to working with the filled posts array. If you are trying to use it at the same time as your empty console.log call, then you probably need to wait until the postRef promises have all been fulfilled. This can be done with...another promise!

I noticed you are using an arrow function above, so I'll assume you are also open to using ES6 promises. This might be one solution:

'use strict';

function listen() {

  // get current user ID
  var currentUserID = firebase.auth().currentUser.uid

  // query user class to get reference keys to posts
  var postList = firebase.database().ref('/users/'+currentUserID+'/postKeys')

  //create posts array to store posts that are queried
  var posts = []

///// NEW - create promise
  var postListPromise = new Promise(function(resolve, reject) {

      // query the post class
      postList.once('value').then(function(snapshot) {

        snapshot.forEach((child)=> {
          // get postID
          var postID = child.val().postID
          // append postID to get location of post data
          var postRef = firebase.database().ref('posts/'+postID)
          // get post data
          postRef.once('value').then(function(postSnap) {
             // save data to variable post 
            ///// NEW - added 'var' to this line, just in case you forgot
            var post = postSnap.val()

            // push post object to array
            posts.push(post);
            // setState here returns desired value, but probably bad practice because I set the state as many times as there are posts.

            ///// NEW - resolve promise when at last snapshot item
            if (posts.length === snapshot.length) {
              resolve(posts);
            }

          })
        }) //.then here returns error

      }) // .then here returns empty array

///// NEW - just closing braces
  });

///// NEW - Handle fullfilled promise
  postListFullPromise.then(function(fullPostList){
    console.log( JSON.stringify(fullPostList) ); // This should show the full list, so continue working with posts here
  });
}

Hope that helps!

3 Answers

Joseph Dalton
Joseph Dalton
12,489 Points

Awesome! I'm glad I could help Aaron.

About using on() instead of once(), it looks like the on() method does not return a promise object like once() method, or the other methods mentioned in the blog post. I also checked the Firebase docs for on() (here), and it looks like, just like you said, you'll have to go back to using callbacks if you need to keep watching the event.

Joseph Dalton - absolutely incredible...thank you. One more question for you now that this approach works (and thanks for explaining the empty array in the console situation...that boggled my mind). I need to listen for changes when a new post is added to update the feed. I'm unclear from this post (https://firebase.googleblog.com/2016/01/keeping-our-promises-and-callbacks_76.html), why I cannot use .on instead of .once. In all of the examples where people are using Firebase promises it appears they are using .once, however .on is needed in my case. Would this mean I need to resort to a callback instead of a promise?

thanks again for your help. I really appreciate it.

Joseph Dalton you're amazing. thanks a million.