Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular $q.all gets called after first promise finished

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
like image 201
Miquel Avatar asked Jan 19 '16 12:01

Miquel


1 Answers

$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 composite promise, that is a promise that waits for all the promises to complete 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:

  1. A $q.all promise is not resilient. It is rejected with the first rejected promise.
  2. 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

like image 57
georgeawg Avatar answered Sep 25 '22 13:09

georgeawg