Angularjs $q.all

AngularjsPromiseQ

Angularjs Problem Overview


I have implemented the $q.all in angularjs, but I can not make the code work. Here is my code :

UploadService.uploadQuestion = function(questions){

		var promises = [];

		for(var i = 0 ; i < questions.length ; i++){

			var deffered  = $q.defer();
			var question  = questions[i]; 
		
			$http({

				url   : 'upload/question',
				method: 'POST',
				data  : question
			}).
			success(function(data){
				deffered.resolve(data);
			}).
			error(function(error){
				deffered.reject();
			});

			promises.push(deffered.promise);
		}
		
		return $q.all(promises);
	}

And here is my controller which call the services:

uploadService.uploadQuestion(questions).then(function(datas){
      
   //the datas can not be retrieved although the server has responded    
}, 
function(errors){ 
   //errors can not be retrieved also
    
})

I think there is some problem setting up $q.all in my service.

Angularjs Solutions


Solution 1 - Angularjs

In javascript there are no block-level scopes only function-level scopes:

Read this article about javaScript Scoping and Hoisting.

See how I debugged your code:

var deferred = $q.defer();
deferred.count = i;

console.log(deferred.count); // 0,1,2,3,4,5 --< all deferred objects

// some code

.success(function(data){
   console.log(deferred.count); // 5,5,5,5,5,5 --< only the last deferred object
   deferred.resolve(data);
})
  • When you write var deferred= $q.defer(); inside a for loop it's hoisted to the top of the function, it means that javascript declares this variable on the function scope outside of the for loop.
  • With each loop, the last deferred is overriding the previous one, there is no block-level scope to save a reference to that object.
  • When asynchronous callbacks (success / error) are invoked, they reference only the last deferred object and only it gets resolved, so $q.all is never resolved because it still waits for other deferred objects.
  • What you need is to create an anonymous function for each item you iterate.
  • Since functions do have scopes, the reference to the deferred objects are preserved in a closure scope even after functions are executed.
  • As #dfsq commented: There is no need to manually construct a new deferred object since $http itself returns a promise.

Solution with angular.forEach:

Here is a demo plunker: http://plnkr.co/edit/NGMp4ycmaCqVOmgohN53?p=preview

UploadService.uploadQuestion = function(questions){

    var promises = [];

    angular.forEach(questions , function(question) {

        var promise = $http({
            url   : 'upload/question',
            method: 'POST',
            data  : question
        });

        promises.push(promise);
    
    });

    return $q.all(promises);
}
My favorite way is to use Array#map:

Here is a demo plunker: http://plnkr.co/edit/KYeTWUyxJR4mlU77svw9?p=preview

UploadService.uploadQuestion = function(questions){

    var promises = questions.map(function(question) {

        return $http({
            url   : 'upload/question',
            method: 'POST',
            data  : question
        });
    
    });

    return $q.all(promises);
}

Solution 2 - Angularjs

$http is a promise too, you can make it simpler:

return $q.all(tasks.map(function(d){
        return $http.post('upload/tasks',d).then(someProcessCallback, onErrorCallback);
    }));

Solution 3 - Angularjs

The issue seems to be that you are adding the deffered.promise when deffered is itself the promise you should be adding:

Try changing to promises.push(deffered); so you don't add the unwrapped promise to the array.

 UploadService.uploadQuestion = function(questions){
    
            var promises = [];
    
            for(var i = 0 ; i < questions.length ; i++){
    
                var deffered  = $q.defer();
                var question  = questions[i]; 
    
                $http({
    
                    url   : 'upload/question',
                    method: 'POST',
                    data  : question
                }).
                success(function(data){
                    deffered.resolve(data);
                }).
                error(function(error){
                    deffered.reject();
                });
    
                promises.push(deffered);
            }
    
            return $q.all(promises);
        }

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
Questionthemyth92View Question on Stackoverflow
Solution 1 - AngularjsIlan FrumerView Answer on Stackoverflow
Solution 2 - AngularjsZerkotinView Answer on Stackoverflow
Solution 3 - AngularjsDavin TryonView Answer on Stackoverflow