Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Waiting for a forEach to finish before return from my promise / function

I am using Firebase Cloud Firestore, however, I think this may be more of a JavaScript asynchronous vs synchronous promise return issue.

I am doing a query to get IDs from one collection, then I am looping over the results of that query to lookup individual records from another collection based on that ID.

Then I want to store each found record into an array and then return the entire array.

results.length is always 0 because return results fires before the forEach completes. If I print results.length from inside the forEach it has data.

How can I wait until the forEach is done before returning from the outer promise and the outer function itself?

         getFacultyFavoritesFirebase() {
            var dbRef = db.collection("users").doc(global.user_id).collection("favorites");
            var dbQuery = dbRef.where("type", "==", "faculty");
            var dbPromise = dbQuery.get();
            var results = [];
            return dbPromise.then(function(querySnapshot) {
                querySnapshot.forEach(function(doc) {
                  var docRef = db.collection("faculty").doc(doc.id);
                  docRef.get().then(function(doc) {
                    if (doc.exists) {
                        results.push(doc);
                    }
                  })
                });
                console.log(results.length);
                return results;
            })
            .catch(function(error) {
                console.log("Error getting documents: ", error);
            });
          }
like image 361
AdamG Avatar asked Dec 10 '17 20:12

AdamG


2 Answers

The trick here is to populate results with promises rather than the result. You can then call Promise.all() on that array of promises and get the results you want. Of course, you can't check if doc.exists before pushing the promise so you will need to deal with that once Promise.all() resolves. For example:

function getFacultyFavoritesFirebase() {
    var dbRef = db.collection("users").doc(global.user_id).collection("favorites");
    var dbQuery = dbRef.where("type", "==", "faculty");
    var dbPromise = dbQuery.get();
    // return the main promise
    return dbPromise.then(function(querySnapshot) {
        var results = [];
        querySnapshot.forEach(function(doc) {
            var docRef = db.collection("faculty").doc(doc.id);
            // push promise from get into results
            results.push(docRef.get())
        });
        // dbPromise.then() resolves to  a single promise that resolves 
        // once all results have resolved
        return Promise.all(results)
    })
    .catch(function(error) {
        console.log("Error getting documents: ", error);
    });
}

getFacultyFavoritesFirebase
.then(results => {
    // use results array here and check for .exists
}
like image 178
Mark Avatar answered Oct 12 '22 23:10

Mark


If you have multiple items of work to perform at the same time that come from a loop, you can collect all the promises from all the items of work, and wait for them all to finish with Promise.all(). The general form of a possible solution looks like this:

const promises = []  // collect all promises here
items.forEach(item => {
    const promise = item.doWork()
    promises.push(promise)
})
Promise.all(promises).then(results => {
    // continue processing here
    // results[0] is the result of the first promise in the promises array
})

You can adapt this to something that suits your own specific form.

like image 21
Doug Stevenson Avatar answered Oct 12 '22 23:10

Doug Stevenson