NameTopSecret
NameTopSecret

Reputation: 332

How to make JavaScript promise wait for another promise to be resolved inside for() loop

I have an AngularJS application, which I use promises to get data from firebase database.

Here is my home-controller:

$scope.wallets;

walletDAO.getWalletsByUserId(auth.uid)
.then(function(wallets){
    $scope.wallets = wallets;
    $scope.$apply();
})
.catch(function(error){
    console.log(error);
});

These are my two methods inside an service I call walletDAO:

this.getWalletsByUserId = function(id) {
    return new Promise(function(resolve, reject) {
        //codigo aqui
        var dbRef = database.ref("users/" + auth.currentUser.uid);
        dbRef.on('value', function(data) {
            //console.log("Wallet IDs Retrieved!");
            var userOnDB = data.val();
            var walletIds = userOnDB.wallets;
            var wallets = [];
            for (i = 0; i < walletIds.length; i++) {
                var x = getWalletById(walletIds[i])
                .then(function(wallet){
                    wallets.push(wallet);
                })
                .catch(function(error){
                    console.log(error);
                });
            }
            resolve(wallets);
        }, function(error) {
            reject(error);
        });
    });
};

var getWalletById = function(id) {
    return new Promise(function(resolve, reject) {
        var dbRef = database.ref("wallets/" + id);
        dbRef.on('value', function(data) {
            //console.log("Wallet Retrieved!");
            var wallet = data.val();
            //console.log(wallet);
            resolve(wallet);
        }, function(error) {
            reject(error);
        });
    });
};

The second method, getWalletById, receive an wallet ID and return an wallet object from the firebase database. this mehod is called on the first method, getWalletsByUserId inside a for loop, which should wait for the second method to return the wallet before iterate to the next, so it can push it into the array. The problem is that it dont wait and the code execute the .then() method on the home-controller before the resolving of the getWalletById, leaving the $scope.wallets empty.

Any advice?

Upvotes: 0

Views: 1239

Answers (2)

georgeawg
georgeawg

Reputation: 48968

Instead of manufacturing an ES6 promise from the ref.on method, use the ref.once method and bring it into the AngularJS execution context with $q.when:

function getWalletById(id) {
    var dbRef = database.ref("wallets/" + id);
    var es6Promise = dbRef.once('value');
    return $q.when(es6Promise);
}

Only operations which are applied in the AngularJS execution context will benefit from AngularJS data-binding, exception handling, property watching, etc.


Use $q.all and promise chaining in the parent function:

this.getWalletsByUserId = function(id) {
    var dbRef = database.ref("users/" + auth.currentUser.uid);
    var es6Promise = dbRef.once('value')
      .then(function(snapshot)
        //console.log("Wallet IDs Retrieved!");
        var userOnDB = snapshot.val();
        var walletIds = userOnDB.wallets;
        var promiseList = walletIds.map(function(id){
            return getWalletById(id);
        });
        return $q.all(promiseList);
    });
    return $q.when(es6Promise);
};

The .then method returns a new promise which is resolved or rejected via the return value of the successCallback, errorCallback (unless that value is a promise, in which case it is resolved with the value which is resolved in that promise using promise chaining).

Upvotes: 1

Vasily Liaskovsky
Vasily Liaskovsky

Reputation: 2528

Use $q.all() to wait for all sub-promises to complete

$q.all(walletIds.map(function(id){
  return getWalletById(id);
})).then(function(wallets){
  ...
})

Upvotes: 1

Related Questions