Home » vue » How to use async call inside forEach when using firebase calls

How to use async call inside forEach when using firebase calls

Posted by: admin November 26, 2021 Leave a comment

Questions:

the question that I have is that I can’t figure out how to make this code work properly using Firestore (not sure if this is irrelevant).

The actual code is the following:

prestamoItems() {
      var myarray = [];
      var myobject = {};

      //here comes the first async method (works OK)

      fb.prestamosCollection
        .orderBy("fechaPrestamo", "desc")
        .get()
        .then(val => {
          if (!val.empty) {

            //here comes forEach

            val.docs.forEach(doc => {
              myobject = doc.data();
              myobject.id = doc.id;
              console.log("The doc id is " +myobject.id)

              //here comes second async call inside the forEach loop, but it doesnt wait for this 
              //to be finished, and immediately goes to the other step

              fb.equiposCollection.doc(myobject.id).get().then(eqp => {
                console.log("The doc id from the other collection is " +eqp.id)
              })

              myarray.push(myobject)
              console.log("myobject pushed to myarray")
            });


          }
        });
    }

Please note that I’m calling an async method inside a forEach loop that comes from another async method. In every variation of the code, the output that I’m getting (the console logs) are the following:

11:13:14.999 Prestamos.vue?18d2:71 The doc id is 1yTCUKwBvlopXX2suvVu
11:13:14.999 Prestamos.vue?18d2:78 myobject pushed to myarray
11:13:15.000 Prestamos.vue?18d2:71 The doc id is Z5TE15Fj3HFrn1zvceGe
11:13:15.000 Prestamos.vue?18d2:78 myobject pushed to myarray
11:13:15.000 Prestamos.vue?18d2:71 The doc id is JNN9aN65XE1tUTmlzkoJ
11:13:15.000 Prestamos.vue?18d2:78 myobject pushed to myarray
11:13:15.000 Prestamos.vue?18d2:71 The doc id is NF2hHCpM8leZezHbmnJx
11:13:15.001 Prestamos.vue?18d2:78 myobject pushed to myarray
11:13:15.364 Prestamos.vue?18d2:74 The doc id from the other collection is 1yTCUKwBvlopXX2suvVu
11:13:15.368 Prestamos.vue?18d2:74 The doc id from the other collection is Z5TE15Fj3HFrn1zvceGe
11:13:15.374 Prestamos.vue?18d2:74 The doc id from the other collection is JNN9aN65XE1tUTmlzkoJ
11:13:15.379 Prestamos.vue?18d2:74 The doc id from the other collection is NF2hHCpM8leZezHbmnJx

So, the forEach loop is not waiting to the async function inside it (which actually is the expected behavior, AFAIK).

The question is how can I make it wait for the inner call to be finished before adding the obect to the array? Thanks in advance.

Answers:

either you nest code, which depends on previous results into then() callbacks or you wrap the loop (forEach does not support async) in async block to make use of await inside. eg.:

fb.prestamosCollection
  .orderBy("fechaPrestamo", "desc")
    .get()
    .then(val => {
      if (!val.empty) {
        // wrap loop in async function call iife so we can use await inside
        (async () => {
          for (var i = 0; i < val.docs.length; i++) {
            const doc = val.docs[i];
            myobject = doc.data();
            myobject.id = doc.id;
            // this will be synchronous now
            let eqp = await fb.equiposCollection.doc(myobject.id).get();
            console.log(eqp.id);
            myarray.push(myobject)
          }
        })();
      }
    });

###

The root of the problem is that you’re trying to turn an asychronous operation (waiting for Firestore to return values) into a synchronous one. This isn’t really possible in a meaningful way in JavaScript without causing lots of issues!

You’ll need to populate your array inside of the .then() callback and return the promise as a result of the function. Any caller that calls your prestamoItems() function will also have to use .then() callbacks to access the underlying myarray value:

const _ = {
  async prestamoItems() {
    const val = await fb.prestamosCollection.orderBy("fechaPrestamo", "desc").get();
    if (val.empty) {
      return myarray
    }

    // Promise.all() will take a list of promises and will return their results once they have all finished.
    return await Promise.all(
      // Array.prototype.map() will take an existing array and, for each item, call the given function and return a new array with the return value of each function in that array.
      // This is functionally equivalent to making a new array and push()ing to it, but it reads a lot nicer!
      val.docs.map(async doc => {
        const myobject = doc.data();
        const eqp = await fp.equiposCollection.doc(myobject.id).get()
        // I presume you want to do something with eqp here
        return myobject
      })
    );
  }
}

The above code sample uses Array.prototype.map() to do away with myarray as it’s not necessary.

A caller would have to use this code like this:

_.prestamoItems().then((myarray) => {
   ...
})

Promises are a way of saying that a value may be avaliable at some point in the future. Because of this, you have to make sure that any interaction you have with a promise is written in such a way that assumes the value is not avaliable immediately. The easiest way to do this is by using async/await and ensuring that you return promise objects.

###

just move the push inside then like this

fb.equiposCollection.doc(myobject.id).get().then(eqp => {
   console.log("The doc id from the other collection is " +eqp.id)

   myarray.push(myobject)
   console.log("myobject pushed to myarray")
})