28

我一直在尝试用 Angular 的 $q 等待几个承诺,但似乎没有选项可以“等待所有承诺,即使承诺被拒绝”。我创建了一个示例(http://jsfiddle.net/Zenuka/pHEf9/21/),我希望在所有承诺都被解决/拒绝时执行一个函数,这可能吗?就像是:

$q.whenAllComplete(promises, function() {....})

编辑:在示例中,您看到第二个服务失败,然后立即$q.all().then(..., function(){...})执行函数。我要等第五个promise完成。

4

8 回答 8

31

好的,我自己实现了一个基本版本(我只想等待一系列承诺)。如果他们愿意,任何人都可以扩展它或创建一个更简洁的版本:-) 检查 jsfiddle 以查看它的实际效果:http: //jsfiddle.net/Zenuka/pHEf9/

angular.module('test').config(['$provide', function ($provide) {
    $provide.decorator('$q', ['$delegate', function ($delegate) {
        var $q = $delegate;

        // Extention for q
        $q.allSettled = $q.allSettled || function (promises) {
            var deferred = $q.defer();
            if (angular.isArray(promises)) {
                var states = [];
                var results = [];
                var didAPromiseFail = false;
                if (promises.length === 0) { 
                    deferred.resolve(results);
                    return deferred.promise;
                }

                // First create an array for all promises with their state
                angular.forEach(promises, function (promise, key) {
                    states[key] = false;
                });

                // Helper to check if all states are finished
                var checkStates = function (states, results, deferred, failed) {
                    var allFinished = true;
                    angular.forEach(states, function (state, key) {
                        if (!state) {
                            allFinished = false;
                        }
                    });
                    if (allFinished) {
                        if (failed) {
                            deferred.reject(results);
                        } else {
                            deferred.resolve(results);
                        }
                    }
                }

                // Loop through the promises
                // a second loop to be sure that checkStates is called when all states are set to false first
                angular.forEach(promises, function (promise, key) {
                    $q.when(promise).then(function (result) {
                        states[key] = true;
                        results[key] = result;
                        checkStates(states, results, deferred, didAPromiseFail);
                    }, function (reason) {
                        states[key] = true;
                        results[key] = reason;
                        didAPromiseFail = true;
                        checkStates(states, results, deferred, didAPromiseFail);
                    });
                });
            } else {
                throw 'allSettled can only handle an array of promises (for now)';
            }

            return deferred.promise;
        };

        return $q;
    }]);
}]);
于 2013-09-19T09:33:03.727 回答
20

类似于如何all()返回解析值的数组/散列,Kris Kowal 的 Q 中的allSettled()函数返回一组对象,这些对象看起来像:

{ state: 'fulfilled', value: <resolved value> }

或者:

{ state: 'rejected', reason: <rejection error> }

由于这种行为相当方便,我已将该函数移植到 Angular.js 的 $q 中:

angular.module('your-module').config(['$provide', function ($provide) {
    $provide.decorator('$q', ['$delegate', function ($delegate) {
        var $q = $delegate;

        $q.allSettled = $q.allSettled || function allSettled(promises) {
            // Implementation of allSettled function from Kris Kowal's Q:
            // https://github.com/kriskowal/q/wiki/API-Reference#promiseallsettled

            var wrapped = angular.isArray(promises) ? [] : {};

            angular.forEach(promises, function(promise, key) {
                if (!wrapped.hasOwnProperty(key)) {
                    wrapped[key] = wrap(promise);
                }
            });

            return $q.all(wrapped);

            function wrap(promise) {
                return $q.when(promise)
                    .then(function (value) {
                        return { state: 'fulfilled', value: value };
                    }, function (reason) {
                        return { state: 'rejected', reason: reason };
                    });
            }
        };

        return $q;
    }]);
}]);

归功于:

于 2014-11-24T21:36:41.750 回答
4

angularJS 中的 promise API 基于https://github.com/kriskowal/q。我查看了Q提供的 API,它有一个方法allSettled,但是这个方法并没有暴露在 AngularJS 使用的端口上。这是文档的形式

all 函数返回一个值数组的承诺。当这个 Promise 被实现时,数组包含原始 Promise 的实现值,与这些 Promise 的顺序相同。如果给定的 Promise 之一被拒绝,返回的 Promise 将立即被拒绝,而不是等待批处理的其余部分。如果你想等待所有的承诺被履行或拒绝,你可以使用 allSettled。

于 2013-09-19T07:34:29.903 回答
3

我最近解决了同样的问题。这就是问题所在:

  • 我有一系列的承诺要处理,promises
  • 我想得到所有的结果,解决或拒绝
  • 我希望承诺并行运行

这就是我解决问题的方法:

promises = promises.map(
    promise => promise.catch(() => null)
);
$q.all(promises, results => {
    // code to handle results
});

这不是一般的修复方法,但它简单易懂。当然,如果您的任何承诺可以解决为 null,那么您无法区分拒绝,但它在许多情况下都有效,您始终可以修改 catch 函数以处理您正在解决的特定问题。

于 2016-09-23T11:10:41.980 回答
1

Thanks for the inspiration Zenuka, you can find my version at https://gist.github.com/JGarrido/8100714

Here it is, in it's current state:

.config( function($provide) {
  $provide.decorator("$q", ["$delegate", function($delegate) {
    var $q = $delegate;

    $q.allComplete = function(promises) {

      if(!angular.isArray(promises)) {
        throw Error("$q.allComplete only accepts an array.");
      }

      var deferred = $q.defer();
      var passed = 0;
      var failed = 0;
      var responses = [];

      angular.forEach(promises, function(promise, index) {
        promise
          .then( function(result) {
            console.info('done', result);
            passed++;
            responses.push(result);
          })
          .catch( function(result) {
            console.error('err', result);
            failed++;
            responses.push(result);
          })
          .finally( function() {
            if((passed + failed) == promises.length) {
              console.log("COMPLETE: " + "passed = " + passed + ", failed = " + failed);

              if(failed > 0) {
                deferred.reject(responses);
              } else {
                deferred.resolve(responses);
              }
            }
          })
        ;
      });

      return deferred.promise;

    };

    return $q;
  }]);
})
于 2013-12-23T17:20:34.907 回答
1

解决这个问题的更简单的方法。

$provide.decorator('$q', ['$delegate', function ($delegate) {
    var $q = $delegate;

    $q.allSettled = $q.allSettled || function (promises) {
        var toSettle = [];

        if (angular.isArray(promises)) {
            angular.forEach(promises, function (promise, key) {
                var dfd = $q.defer();
                promise.then(dfd.resolve, dfd.resolve);
                toSettle.push(dfd.promise);
            });
        }

        return $q.all(toSettle);
    };

    return $q;
}]);
于 2014-08-18T15:18:35.397 回答
0

一个简单的解决方案是使用 catch() 来处理任何错误并阻止拒绝传播。您可以通过不从 catch() 返回值或使用错误响应解决然后在 all() 中处理错误来做到这一点。这样 $q.all() 将始终被执行。我用一个非常简单的例子更新了小提琴:http: //jsfiddle.net/pHEf9/125/

...
function handleError(response) {
    console.log('Handle error');
}

// Create 5 promises
var promises = [];
var names = [];
for (var i = 1; i <= 5; i++) {
    var willSucceed = true;
    if (i == 2) willSucceed = false;
    promises.push(
      createPromise('Promise' + i, i, willSucceed).catch(handleError));
}
...

请注意,如果您没有从 catch() 中返回值,则传递给 all() 的已解析承诺数组将包含那些错误元素的未定义。

于 2015-02-10T02:38:18.457 回答
-3

最后使用

$q.all(tasks).finally(function() {
                            // do stuff
                        });
于 2016-04-13T16:29:55.870 回答