stefan
stefan

Reputation: 59

Firebase (Angular) Promises with Loops in Loops

How do you execute a function only after all the promises are resolved where you have to wait for async calls in loops within loops?

The code is simplified to the minimum

$scope.book = function(input) {

  //Get the cart items from DB
  ref.child('/cart/' + userId).once('value').then(function(cartSnap) {

    //Loop through the cart items
    cartSnap.forEach(function(cartItemSnap) {

      var itemId = cartItemSnap.key();

      //Get the inventory items from DB that are in the cart
      ref.child('/inventory/' + itemId).once('value').then(function(inventoryItem) {

        //Loop through booking IDs of inventoryItem
        inventoryItem.child('rentals').forEach(function(rentalSnap) {

          var rentalId = rentalSnap.key();

          //Get bookings from rental/active
          ref.child('/rental/'+ rentalId).once('value').then(function(activeRentals) {

            checkIfBookingIsAllowed();

          });
        });
      });
    });

    //Once everything was checked
    bookRental();
  });
};

To improve speed all the requests can be made in parallel but the final function bookRental() can only be called when everything is resolved.

Thanks for your help.

EDITED: Another try that failed. The Promise.all('collector') gets fired before all the promises are resolved. So 'done' shows up before all the 'checks' in the console.

$scope.book = function(input) {

  //Get the cart items from DB
  ref.child('/cart/' + userId).once('value').then(function(cartSnap) {

    //Promise collector
    var collector = [];

    //Loop through the cart items
    cartSnap.forEach(function(cartItemSnap) {

      var itemId = cartItemSnap.key();

      //Get the inventory items from DB that are in the cart
      var promise1 = ref.child('/inventory/' + itemId).once('value').then(function(inventoryItem) {

        //Loop through booking IDs of inventoryItem
        inventoryItem.child('rentals').forEach(function(rentalSnap) {

          var rentalId = rentalSnap.key();

          //Get bookings from rental/active
          var promise2 = ref.child('/rental/'+ rentalId).once('value').then(function(activeRentals) {

            console.log('check');
            collector.push(promise2);

          });
        });

        collector.push(promise1);
      });
    });

    //Once everything was checked
    Promise.all(collector).then(function() {
      console.log('Done');
    });
  });
};

Upvotes: 2

Views: 886

Answers (1)

jib
jib

Reputation: 42530

You're collecting the promises too late. Collect them now, not later. Code inside .thens runs later.

Here's what runs immediately:

  1. Both nested for-loops run to completion, minus all code inside .thens.
  2. Promise.all(collector).

At this point collector is still empty, so Promise.all() completes real fast.

Move the collector.push(promise) calls outside of the .thens.

A more functional approach:

With the above fix, your code should work, but a cleaner approach is to return all promises. While forEach doesn't allow return values, map does, so this can be re-written as (simplifying):

Promise.all(cartSnap.map(snap => ref.child(itemUrl).once('value').then(item =>
  Promise.all(item.child('rentals').map(snap => ref.child(url).once('value'))))))
.then(allActiveRentals => console.log('Done'));

Upvotes: 2

Related Questions