AngularJS Array of Promises

Daniel Bonnell picture Daniel Bonnell · Sep 21, 2015 · Viewed 10.1k times · Source

Can anyone help me with some Angular promises? I have the following functions that should take in an array of file objects, iterate over them, and upload each one. During each iteration, a promise object is pushed to an array of promises. Within my upload function I have a cycle function with a .then() attached, which should not be called until all the promise objects have resolved. I think my code looks correct, but it's not working right. The images upload, but cycle(files).then() is being called immediately, rather than once the promises array resolves.

function upload(files) {
    var uploadCount = files.length;
    function cycle(files) {
        var promises = [];
        for (var i = 0; i < files.length; i++) {
            var deferred = $q.defer();
            promises.push(deferred);
            var file = files[i];
            Upload.upload({
                url: '/photos.json',
                file: file
            }).success(function(){
                $scope.progressCurrentCount += 1;
                deferred.resolve();
            });
        };
        return $q.all(promises);
    };

    cycle(files).then(function(result) {
        if(uploadCount > 1) {
            $scope.lastAction = 'uploaded ' + uploadCount + ' photos';
        } else {
            $scope.lastAction = 'uploaded ' + uploadCount + ' photo';
        }
        $scope.showSuccessModal = true;
        $scope.uploading = false;
        $scope.failedUploads = [];
        $scope.newPhotos = {
            token: $scope.token,
            files: undefined
        };
        photoUploadBtn.removeClass('disabled');
    })
};

Final working code*

Rather than setting var deferred = $q.defer(); pushing deferred.promise into the promises array, and then resolving deferred in my .success() callback, which wasn't working, I just push my Upload.upload() function without the .success() callback into promises, and then pass that to $q.all(), which does all the lifting.

function upload(files) {
    var uploadCount = files.length;
    function cycle(files) {
        var promises = [];
        for (var i = 0; i < files.length; i++) {
            var file = files[i];
            var promise = Upload.upload({
                url: '/photos.json',
                file: file
            });
            promises.push(promise);
        };
        return $q.all(promises);
    };

    cycle(files).then(function(result) {
        if(uploadCount > 1) {
            $scope.lastAction = 'uploaded ' + uploadCount + ' photos';
        } else {
            $scope.lastAction = 'uploaded ' + uploadCount + ' photo';
        };
        $scope.showSuccessModal = true;
        $scope.uploading = false;
        $scope.failedUploads = [];
        $scope.newPhotos = {
            token: $scope.token,
            files: undefined
        };
        photoUploadBtn.removeClass('disabled');
        getPhotos(q);
    })
};

Answer

DzinX picture DzinX · Sep 21, 2015

You must push a promise, not a deferred, to the promises array:

promises.push(deferred.promise);

It helps if you think about it like this:

  • the promise is a read-only object you want to return to the consumers
  • deferred is the modifier of that read-only promise and you want to keep it for yourself