I'm trying to use $q.all to wait until all promises are resolved but it's called after first promise is finished!
What I'm doing wrong?
function sendAudits(audits) {
var promises = [];
$scope.sendAudits = {
progress: 0
};
angular.forEach(audits, function (audit, idAudit) {
promises.push(saveAudit(audit));
});
$q
.all(promises)
.then(function (data) {
console.log(data);
}, function (errors) {
console.log(errors);
});
}
function saveAudit(audit) {
var filename = audit.header.id + ".txt";
return $http({
method: 'PUT',
url: '/audits/audits.php?filename=' + encodeURIComponent(filename),
data: AuditSvc.getPlainAudit(audit.header.id)
}).finally(function () {
$scope.sendAudits.progress += 1;
console.log("FINALLY: " + audit.header.id);
});
}
EDIT
Analysing a little bit deeper the problem, this situation occurs when some of the responses are error. For example when the server returns header("HTTP/1.0 418 I'm A Teapot: " . $filename);
, client console would be like:
PUT http://localhost:8182/audits/audits.php?filename=1.txt 418 (I'm A Teapot: 1.txt)
FINALLY: 1
Object {data: "", status: 418, config: Object, statusText: "I'm A Teapot: 1.txt"}
PUT http://localhost:8182/audits/audits.php?filename=2.txt 418 (I'm A Teapot: 2.txt)
FINALLY: 2
PUT http://localhost:8182/audits/audits.php?filename=3.txt 418 (I'm A Teapot: 3.txt)
FINALLY: 3
PUT http://localhost:8182/audits/audits.php?filename=4.txt 418 (I'm A Teapot: 4.txt)
FINALLY: 4
I'm trying to use $q.all to wait until all promises are resolved but it's called after first promise is finished!
What I'm doing wrong?
function sendAudits(audits) {
var promises = [];
$scope.sendAudits = {
progress: 0
};
angular.forEach(audits, function (audit, idAudit) {
promises.push(saveAudit(audit));
});
$q
.all(promises)
.then(function (data) {
console.log(data);
}, function (errors) {
console.log(errors);
});
}
function saveAudit(audit) {
var filename = audit.header.id + ".txt";
return $http({
method: 'PUT',
url: '/audits/audits.php?filename=' + encodeURIComponent(filename),
data: AuditSvc.getPlainAudit(audit.header.id)
}).finally(function () {
$scope.sendAudits.progress += 1;
console.log("FINALLY: " + audit.header.id);
});
}
EDIT
Analysing a little bit deeper the problem, this situation occurs when some of the responses are error. For example when the server returns header("HTTP/1.0 418 I'm A Teapot: " . $filename);
, client console would be like:
PUT http://localhost:8182/audits/audits.php?filename=1.txt 418 (I'm A Teapot: 1.txt)
FINALLY: 1
Object {data: "", status: 418, config: Object, statusText: "I'm A Teapot: 1.txt"}
PUT http://localhost:8182/audits/audits.php?filename=2.txt 418 (I'm A Teapot: 2.txt)
FINALLY: 2
PUT http://localhost:8182/audits/audits.php?filename=3.txt 418 (I'm A Teapot: 3.txt)
FINALLY: 3
PUT http://localhost:8182/audits/audits.php?filename=4.txt 418 (I'm A Teapot: 4.txt)
FINALLY: 4
Share
Improve this question
edited Jan 19, 2016 at 17:38
Miquel
asked Jan 19, 2016 at 12:23
MiquelMiquel
9,02911 gold badges63 silver badges84 bronze badges
2
- 1 What do you see in the console? – yeouuu Commented Jan 19, 2016 at 12:29
-
The response for the first call to saveAudit: first $http call (audits has 4 elements). The strange is that progress get's raised 4 times in
finally
(one for each audit). – Miquel Commented Jan 19, 2016 at 13:51
2 Answers
Reset to default 3$q.all
is not resilient
As noted by others, $q.all
is not resilient. If one of the promises is rejected, the $q.all
is rejected with the first error.
To create a resilient posite promise, that is a promise that waits for all the promises to plete pass or fail, use .catch
on each individual promise to convert the rejected promise to a successful promise.
var resilientPromises = [];
angular.forEach(promises, function(p) {
var resilientP = p.catch( function(result) {
//return to convert rejection to success
return result;
});
resilientPromises.push(resilientP);
});
$q.all(resilientPromises).then( function (results) {
//process results
});
The two things to take away from this answer:
- A
$q.all
promise is not resilient. It is rejected with the first rejected promise. - A fulfilled promise can be created from a rejected promise by returning a value to the onRejected function of either the
.then
method or the.catch
method.
For more information, see You're Missing the Point of Promises
The angular documentation doesn't go into detail, but I believe $q.all()
behaves in this case the same way as the es2015 Promise.all()
:
If any of the passed in promises rejects, the all Promise immediately rejects with the value of the promise that rejected, discarding all the other promises whether or not they have resolved.
Most likely what is happening here is that at least one of your requests is failing. Your log statements don't distinguish whether the $q.all()
succeeded or failed but if it is failing all you will see is the first error.
See https://developer.mozilla/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all for the source of the quote.
Edit:
If you want to get all of the responses, even when some fail, then you should add a catch
handler in saveAudit
to convert the failures into successful responses:
function saveAudit(audit) {
var filename = audit.header.id + ".txt";
return $http({
method: 'PUT',
url: '/audits/audits.php?filename=' + encodeURIComponent(filename),
data: AuditSvc.getPlainAudit(audit.header.id)
}).catch(function(error) {
return { error:error};
})
.finally(function () {
$scope.sendAudits.progress += 1;
console.log("FINALLY: " + audit.header.id);
});
}
and then you need to check each response to see whether it contains an error or valid data.