Angularjs $q.all

165,702

Solution 1

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

$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

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);
        }
Share:
165,702

Related videos on Youtube

themyth92
Author by

themyth92

AngularJS and NodeJS developer. Passion in JavaScript.

Updated on July 08, 2022

Comments

  • themyth92
    themyth92 almost 2 years

    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.

    • Davin Tryon
      Davin Tryon over 10 years
      What behaviour are you seeing? Does it call into your then(datas)? Try to just push this: promises.push(deffered);
    • Ilan Frumer
      Ilan Frumer over 10 years
      @themyth92 have you tried my solution?
    • themyth92
      themyth92 over 10 years
      I have tried and both method works on my case. But I will make @Llan Frumer as the correct answer. Really thank you both of you.
    • Pete Alvin
      Pete Alvin over 8 years
      Why are you promise-izing an existing promise? $http already returns a promise. Use of $q.defer is superfluous.
    • Christophe Roussy
      Christophe Roussy about 7 years
      It is deferred not deffered :)
  • Ilan Frumer
    Ilan Frumer over 10 years
    This only returns an array of deffered objects, I checked it.
  • Ilan Frumer
    Ilan Frumer over 10 years
    I don't know what he says, only what the console says, You can see it's not working: plnkr.co/edit/J1ErNncNsclf3aU86D7Z?p=preview
  • Ilan Frumer
    Ilan Frumer over 10 years
    Also the documentation says clearly that $q.all gets promises not deferred objects. The real problem of the OP is with scoping and because that only the last deferred is getting resolved
  • dfsq
    dfsq over 10 years
    Good answer. One addition: no need to construct new deferred since $http itself returns promise. So it can be shorter: plnkr.co/edit/V3gh7Roez8WWl4NKKrqM?p=preview
  • themyth92
    themyth92 over 10 years
    "When you write var deferred= $q.defer(); inside a for loop it's hoisted to the top of the function.". I dont understand this part, can you explain the reason behind it ?
  • dfsq
    dfsq over 10 years
    I know, I would do the same actually.
  • Ross Rogers
    Ross Rogers about 10 years
    Ilan, thanks for disentangling defer objects and promises. You fixed my all() issue as well.
  • Roamer-1888
    Roamer-1888 almost 10 years
    Yes, the accepted answer is just an agglomeration of anti-patterns.
  • Roamer-1888
    Roamer-1888 almost 10 years
    I think you can leave the .then() clause out as the OP wants to do all that in his controller, but the principle is totally correct.
  • Zerkotin
    Zerkotin almost 10 years
    of course you can omit the then clause, i added it in case you want to log all the HTTP requests, i always add them and use a global onError callback, to handle all server exceptions.
  • Benjamin Gruenbaum
    Benjamin Gruenbaum almost 10 years
    @Zerkotin you can throw from a .then in order to both handle it later and expose it to $exceptionHandler, which should save you that trouble and a global.
  • Zerkotin
    Zerkotin almost 10 years
    the problem was resolved in 2 answers, the problem is scoping or variable hoisting, whatever you want to call it.
  • Drumbeg
    Drumbeg almost 9 years
    Love the use of map to build an array of promises. Very simple and concise.
  • Niko Bellic
    Niko Bellic over 8 years
    Legendary answer! Thanks to your explanation, I can see that since you are pushing the promise returned by the $http call directly into your array of promises, you no longer need a separate scope to save the reference to the unnecessary, intermediary deferred. This means the OP could technically go back to using a simple for loop, although I must admit that using "map" seems to be the most elegant way to create an array of promises in this case.
  • Tisha
    Tisha over 8 years
    Great answer. Let's say this wasn't a POST but a GET method. How do you iterate over the promises to get data belonging to each promise?
  • Niko Bellic
    Niko Bellic almost 8 years
    Nice. This is essentially the same approach as the accepted answer's last solution/example.
  • Spencer
    Spencer almost 8 years
    It should be noted that the declaration is hoisted, but the assignment stays where it is. Also, there is now block-level scoping with the 'let' statement. See developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
  • Jimenemex
    Jimenemex over 5 years
    Would .then fire for every promise of e? How would I get someCallback to fire just when all of the promises return? Add it to $q.all(promises).then(someCallBack)?