angular $q, How to chain multiple promises within and after a for-loop

AngularjsPromiseAngular PromiseDeferred

Angularjs Problem Overview


I want to have a for-loop which calls async functions each iteration.

After the for-loop I want to execute another code block, but not before all the previous calls in the for-loop have been resolved.

My problem at the moment is, that either the code-block after the for-loop is executed before all async calls have finished OR it is not executed at all.

The code part with the FOR-loop and the code block after it (for complete code, please see fiddle):

[..]
function outerFunction($q, $scope) {
    var defer = $q.defer();    
    readSome($q,$scope).then(function() {
        var promise = writeSome($q, $scope.testArray[0])
        for (var i=1; i < $scope.testArray.length; i++) {
             promise = promise.then(
                 angular.bind(null, writeSome, $q, $scope.testArray[i])
             );                                  
        } 
        // this must not be called before all calls in for-loop have finished
        promise = promise.then(function() {
            return writeSome($q, "finish").then(function() {
                console.log("resolve");
                // resolving here after everything has been done, yey!
                defer.resolve();
            });   
        });        
    });   

    return defer.promise;
}

I've created a jsFiddle which can be found here http://jsfiddle.net/riemersebastian/B43u6/3/.

At the moment it looks like the execution order is fine (see the console output).

My guess is, that this is simply because every function call returns immediately without doing any real work. I have tried to delay the defer.resolve with setTimeout but failed (i.e. the last code block was never executed). You can see it in the outcommented block in the fiddle.

When I use the real functions which write to file and read from file, the last code block is executed before the last write operation finishes, which is not what I want.

Of course, the error could be in one of those read/write functions, but I would like to verify that there is nothing wrong with the code I have posted here.

Angularjs Solutions


Solution 1 - Angularjs

What you need to use is $q.all which combines a number of promises into one which is only resolved when all the promises are resolved.

In your case you could do something like:

function outerFunction() {

    var defer = $q.defer();
    var promises = [];

    function lastTask(){
        writeSome('finish').then( function(){
            defer.resolve();
        });
    }

    angular.forEach( $scope.testArray, function(value){
        promises.push(writeSome(value));
    });

    $q.all(promises).then(lastTask);

    return defer.promise;
}

Solution 2 - Angularjs

With the new ES7 you can have the same result in a much more straightforward way:

let promises =  angular.forEach( $scope.testArray, function(value){
    writeSome(value);
});

let results = await Promise.all(promises);

console.log(results);

Solution 3 - Angularjs

You can use $q and 'reduce' together, to chain the promises.

function setAutoJoin() {
    var deferred = $q.defer(), data;
    var array = _.map(data, function(g){
            return g.id;
        });

    function waitTillAllCalls(arr) {
        return arr.reduce(function(deferred, email) {
            return somePromisingFnWhichReturnsDeferredPromise(email);
        }, deferred.resolve('done'));
    }

    waitTillAllCalls(array);

    return deferred.promise;
}

Solution 4 - Angularjs

This worked for me using the ES5 syntax

function outerFunction(bookings) {

	var allDeferred = $q.defer();
	var promises = [];

	lodash.map(bookings, function(booking) {
		var deferred = $q.defer();

		var query = {
			_id: booking.product[0].id,
			populate: true
		}

        Stamplay.Object("product").get(query)
        .then(function(res) {
        	booking.product[0] = res.data[0];
        	deferred.resolve(booking)
        })
        .catch(function(err) {
        	console.error(err);
        	deferred.reject(err);
        });
        
        promises.push(deferred.promise);
	});

	$q.all(promises)
	.then(function(results) { allDeferred.resolve(results) })
	.catch(function(err) { allDeferred.reject(results) });

	return allDeferred.promise;
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSebastianRiemerView Question on Stackoverflow
Solution 1 - AngularjsGruff BunnyView Answer on Stackoverflow
Solution 2 - AngularjsMaurizio In denmarkView Answer on Stackoverflow
Solution 3 - AngularjsSTEELView Answer on Stackoverflow
Solution 4 - AngularjsBen CochraneView Answer on Stackoverflow