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 thefor 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);
}
Related videos on Youtube
Comments
-
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 over 10 yearsWhat behaviour are you seeing? Does it call into your
then(datas)
? Try to justpush
this:promises.push(deffered);
-
Ilan Frumer over 10 years@themyth92 have you tried my solution?
-
themyth92 over 10 yearsI 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 over 8 yearsWhy are you promise-izing an existing promise? $http already returns a promise. Use of $q.defer is superfluous.
-
Christophe Roussy about 7 yearsIt is
deferred
notdeffered
:)
-
-
Ilan Frumer over 10 yearsThis only returns an array of deffered objects, I checked it.
-
Ilan Frumer over 10 yearsI 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 over 10 yearsAlso 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 over 10 yearsGood 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 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 over 10 yearsI know, I would do the same actually.
-
Ross Rogers about 10 yearsIlan, thanks for disentangling
defer
objects andpromises
. You fixed myall()
issue as well. -
Roamer-1888 almost 10 yearsYes, the accepted answer is just an agglomeration of anti-patterns.
-
Roamer-1888 almost 10 yearsI 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 almost 10 yearsof 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 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 almost 10 yearsthe problem was resolved in 2 answers, the problem is scoping or variable hoisting, whatever you want to call it.
-
Drumbeg almost 9 yearsLove the use of
map
to build an array of promises. Very simple and concise. -
Niko Bellic over 8 yearsLegendary 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 over 8 yearsGreat 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 almost 8 yearsNice. This is essentially the same approach as the accepted answer's last solution/example.
-
Spencer almost 8 yearsIt 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 over 5 yearsWould
.then
fire for every promise ofe
? How would I getsomeCallback
to fire just when all of the promises return? Add it to$q.all(promises).then(someCallBack)
?