Angularjs $q.all

我已经在 angularjs 中实现了 $q.all,但是我不能让代码工作:

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);
}

这是我的控制器,它调用服务:

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


})

我认为在我的服务中设置 $q.all 有一些问题。

166166 次浏览

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);
}

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);
}

$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);
}));