Обратный вызов после async forEach AngularJS

Как добавить функцию обратного вызова после async forEach Loop?

Вот какой лучший контекст:

$scope.getAlbums = function(user, callback) {
    $scope.albumsList.forEach(function (obj, i) {
        $scope.getAlbum(user, obj.id, function(value){
            $scope.albums.push(value);
        });
    });
    // callback(); ???
};

$scope.getAlbums('guy123', function(){
    // forEach loop is all done!
    console.log($scope.albums)
});

Контроллер:

.controller('Filters', ['$scope','Imgur', function($scope, Imgur) {

    $scope.getAlbum = function(user, id, callback) {
        Imgur.album.get({user:user, id:id},    
            function(value) {
                return callback(value.data);
            }
        );
    }

    $scope.getAlbums = function(user, callback) {
        // Callback function at end of this forEach loop?
        // Request is async...
        $scope.albumsList.forEach(function (obj, i) {
            $scope.getAlbum(user, obj.id, function(value){
                $scope.albums.push(value);
            });
        });
    };
)]};

Услуги:

.factory('Imgur', function($resource) {
    return {
        album : $resource('https://api.imgur.com/3/account/:user/album/:id')
    }
});

Ответ 1

Как сказал Андрей, использование $q и отложенного объекта должно позволить вам выполнить вашу цель.

Вы хотите использовать $q.all() Это позволит убедиться, что все ваши объекты обещания решены, а затем вы можете перезвонить вашему телефону в .then()

function MyCtrl($scope, $q, $http) {

    $scope.albumsList = [{
            id: 1,
            name: "11"
        }, {
            id: 2,
            name: "22"
        }
    ];
    $scope.albums = [];
    $scope.getAlbum = function(user, id, callback) {
        return $http.get("https://api.imgur.com/3/account/" + user + "/album/" + id).success(
            function(value) {
                return callback(value.data);
            }
        );
    }
    $scope.getAlbums = function (user, callback) {
        var prom = [];
        $scope.albumsList.forEach(function (obj, i) {
            prom.push($scope.getAlbum(user, obj.id, function(value){
                $scope.albums.push(value);
            }));
        });
        $q.all(prom).then(function () {
            callback();
        });
    };
    $scope.getAlbums('guy123', function () {
        alert($scope.albums.length);
    });
}

Пример с этим в jsfiddle

Работает, но не с вызовами $http

С отложенным объектом вы получаете доступ к обещанию, в котором вы можете сменить последовательные вызовы then(). Когда вы разрешите отложенный объект, он выполнит команду foreach, а затем выполнит вашу функцию обратного вызова, которую вы предоставили. Я попытался упростить ваш пример немного дальше, чтобы он работал в jsfiddle.

function MyCtrl($scope, $http, $q) {

    $scope.albumsList = [{
        id: 1,
        name: "11"
    }, {
        id: 2,
        name: "22"
    }];
    $scope.albums = [];
    $scope.getAlbums = function (user, callback) {
        var deferred = $q.defer();
        var promise = deferred.promise;
        promise.then(function () {
            $scope.albumsList.forEach(function (obj, i) {
                $scope.albums.push(obj);
            });
        }).then(function () {
            callback();
        });
        deferred.resolve();
    };
    $scope.getAlbums('guy123', function () {
        alert($scope.albums.length);
    });
}

Пример jsfiddle

Немного больше в отложенном и promises.

Ответ 2

$scope.getAlbums = function(user, callback) {

        var promiseArr = [];
        $scope.albumsList.forEach(function (obj, i) {
            var anHttpPromise = 
            $scope.getAlbum(user, obj.id, function(value){
                $scope.albums.push(value);
            });
            promiseArr.push(anHttpPromise);
        });

        $q.all(promiseArr).then(function(){
            // This callback function will be called when all the promises are resolved.    (when all the albums are retrived)      
        })
    };

    $scope.getAlbum = function(user, id, callback) {
        var anHttpPromise = Imgur.album.get({user:user, id:id},    
            function(value) {
                return callback(value.data);
            }
        );
        return anHttpPromise;
    }

В приведенном выше коде:

  • getAlbum выполняется, чтобы вернуть обещание.
  • Сбор обещаний для каждой итерации списка getAlbums
  • Как только все promises будут собраны, массив обещаний будет передан $q.all
  • Вместо этого метод $q.all возвращает окончательное обещание, чей обратный вызов функция будет запущена, как только все promises в массиве будут решено.

Ответ 3

Используя $resource обещание PR commit, установленный для версии 1.1.3, я смог обернуть $resource вызывает с помощью $q и контролирует поток их асинхронного поведения.

$scope.getAlbum = function(user, id, callback) {
    var promise = Imgur.album.get({user:user, id:id}).$promise.then(
        function( value ){
            return callback(value.data);
        },
        function( error ){
            //Something went wrong!
        }
    )
    return promise;
}

$scope.getAlbums = function(user, callback) {
    var prom = [];
    $scope.albumsList.forEach(function (obj, i) {
        var promise =
        $scope.getAlbum(user, obj.id, function(value){
            $scope.albums.push(value);
        });
        prom.push(promise);

    });

    $q.all(prom).then(function () {
        callback();
    });
};

$scope.getAlbums(user, function(){
    // Totally works, bro.
    console.log($scope.albums);
});

Ответ 4

Похоже, что отложенный http://docs.angularjs.org/api/ng.$q и, в частности, цепочка promises может быть полезно здесь.