最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Angular $q.all gets called after first promise finished - Stack Overflow

programmeradmin2浏览0评论

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
Add a ment  | 

2 Answers 2

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:

  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

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.

发布评论

评论列表(0)

  1. 暂无评论