Подтвердить что ты не робот

AngularJS: $q ждет всех, даже если 1 отклонено

Я пытаюсь подождать пару promises с Angular $q, но, похоже, нет возможности "ждать всех, даже когда отказ от promis". Я создал пример (http://jsfiddle.net/Zenuka/pHEf9/21/), и я хочу, чтобы функция выполнялась, когда все promises разрешены/отклонены, возможно ли это? Что-то вроде:

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

EDIT: в примере вы видите, что вторая служба терпит неудачу, и сразу же после этого выполняется функция в $q.all().then(..., function(){...}). Я хочу дождаться завершения пятого обещания.

4b9b3361

Ответ 1

Хорошо, я сам выполнил базовую версию (я хочу только ждать массив promises). Любой может расширить это или создать более чистую версию, если они захотят:-) Проверьте 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;
                }

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

Ответ 2

Аналогично тому как all() возвращает массив/хэш разрешенных значений, функция allSettled() от Kris Kowal Q возвращает коллекция объектов, которые выглядят так:

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

Кредит:

Ответ 3

API обещаний в angularJS основан на https://github.com/kriskowal/q. Я посмотрел API, который предоставляет Q, и у него был метод allSettled, но этот метод не был обнаружен через порт, который использует AngularJS. Это форма документации

Вся функция возвращает обещание для массива значений. Когда это обещание выполнено, массив содержит значения выполнения оригинал promises, в том же порядке, что и promises. Если один из данный promises отклоняется, возвращенное обещание немедленно отклонен, не дожидаясь остальной части партии. Если вы хотите подождать для того, чтобы все promises выполнялись или отклонялись, вы можете используйте allSettled.

Ответ 4

Спасибо за вдохновение Zenuka, вы можете найти мою версию на https://gist.github.com/JGarrido/8100714

Здесь это, в нем текущее состояние:

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

Ответ 5

Более простой подход к решению этой проблемы.

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

Ответ 6

Я решил эту же проблему недавно. Это была проблема:

  • У меня был массив promises для обработки, promises
  • Я хотел получить все результаты, разрешить или отклонить
  • Я хотел, чтобы promises выполнялся параллельно

Вот как я решил проблему:

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

Это не общее исправление, но это просто и легко. Конечно, если какой-либо из ваших promises может решить null, то вы не можете отличить это отторжение, но он работает во многих случаях, и вы всегда можете изменить функцию catch для работы с конкретной проблемой, которую вы решаете.

Ответ 7

Простым решением было бы использовать catch() для обработки любых ошибок и прекращения отклонения от распространения. Вы можете сделать это, либо не возвращая значение из catch(), либо разрешая использование ответа об ошибке, а затем обрабатывая ошибки во всех(). Таким образом, всегда будет выполняться $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(), массив разрешенных promises, переданных во все(), будет содержать undefined для этих элементов с ошибками.

Ответ 8

просто используйте наконец

$q.all(tasks).finally(function() {
                            // do stuff
                        });