Search code examples
javascriptfirebaseasynchronousgoogle-cloud-firestorees6-promise

Chaining of .then does not work


I am using Firebase for a project and I am facing problems in chaining then().

I am storing the data about a user in an object. One of the attributes of user is an array of references to another set of data named events. I loop through the references to read the data in Firestore (Firebase DB) and store in to my local object called 'user'.

On printing the user object, the output statement of the third then() gets displayed first. Logically each then should be executed after the one above it but the third then() gets executed asynchronously and prints the output first. What is the reason for this? Also, no value is being returned by any of the then(). Is that the source of the problem?

orgRef.collection('collection').doc('doc').get()
  .then(function(data){
  user.info = data.data();
})
  .then(function(){
  user.info.events.forEach(function(event){
    eventRef.get()
      .then(function(data){
      user.eventdata[event] = data.data()[event];
    })
      .then(function(){
      console.log(user);
    });
  });
})
  .then(function(){
  console.log('AT END');
  console.log(user);
});

I have added the output, each console.log statements is printing the same object 'user'. The object gets printed three times because the loop executes two times and prints the object. And the third is because of the then() statement to the main get() promise.

AT END
{ 
  eventdata: {} 
}
{ 
  eventdata:
   { FEENbYcy04k6XPR148rv:
       //more data
   } 
}
{ 
  eventdata:
   { FEENbYcy04k6XPR148rv:
       //more data
     622kUqbF9jftq1nKkQSb:
       //more data  
   } 
}

Solution

  • You need to chain the promises properly instead. As is, your eventRef.get().thens are not connected to your final 'AT END' then.

    Use Promise.all to turn an array of promises into a single one, and then return that promise for the third then.

    orgRef.collection('collection').doc('doc').get()
      .then(function(data) {
        user.info = data.data();
      })
      .then(function() {
        const allPromises = user.info.events.map(function(event) {
          return eventRef.get()
            .then(function(data) {
              user.eventdata[event] = data.data()[event];
            })
            .then(function() {
              console.log(user);
            });
        });
        return Promise.all(allPromises);
      })
      .then(function() {
        console.log('AT END');
        console.log(user);
      });
    

    You could make it a lot more concise too by utilizing ES6 arrow functions and implicit return:

    orgRef.collection('collection').doc('doc').get()
      .then(data => user.info = data.data())
      .then(() => (
        Promise.all(user.info.events.map((event) => (
          eventRef.get()
            .then(data => user.eventdata[event] = data.data()[event])
            .then(() => console.log(user))
        )))
      ))
      .then(() => {
        console.log('AT END');
        console.log(user);
      });