user1027620
user1027620

Reputation: 2785

AngularJS Promise Returns Empty Array

I have this code in a factory:

  getAyahsByJuz: function (juzIndex) {
        var response = [];
        var promises = [];

        var self = this;
        var deferred = $q.defer();
        $timeout(function () {
            $http.get('data/quran.json').success(function (data) {
                var ayahs = Quran.ayah.listFromJuz(juzIndex);
                angular.forEach(ayahs, function (value, key) {
                    var promise = self.getVerse(value.surah, value.ayah).then(function (res) {
                        var verse = {
                            surah: value.surah,
                            ayah: value.ayah,
                            text: res
                        };
                        response.push(verse);
                    }, function (err) {
                        console.log(err);
                    });
                    promises.push(promise);
                });
            });
        }, 30);

        $q.all(promises).then(function() {
            deferred.resolve(response);
        });

        return deferred.promise;
    },

Please note that everything is working fine the verse object is returning properly. However, when I use this in a controller using .then(res). res returns [] instead of the array filled with the verse objects.

Can anyone point out why? Thanks!

Upvotes: 0

Views: 1320

Answers (1)

Gabriel L.
Gabriel L.

Reputation: 1709

The short answer is because your $q.all runs before $timeout & before the $http embedded in $timeout. Let's boil your original code down to its relevant components:

getAyahsByJuz: function (juzIndex) {
    var response = [];
    var promises = [];
    var deferred = $q.defer();
    // ...irrelevant stuff that will happen after a $timeout
    // this happens IMMEDIATELY (before $timeout):
    $q.all(promises).then(function() { // wait for empty promise array
        deferred.resolve(response); // resolve with empty response array
    }); // side note: this is a broken chain! deferred.promise can't reject

    return deferred.promise; // send promise for empty array
}

See the problem? If for some odd reason you need to keep that $timeout, here's the fix with substantial promise refactoring & removing the awful jquery-inspired non-promisy success syntax):

getAyahsByJuz: function (juzIndex) {
    var self = this;
    // $timeout itself returns a promise which we can post-process using its callback return value
    return $timeout(function () {
        // returning the $http promise modifies the $timeout promise
        return $http.get('data/quran.json').then(function (response) { // you never used this response!

            var versePromises = [];

            var ayahs = Quran.ayah.listFromJuz(juzIndex);
            angular.forEach(ayahs, function (value, key) {
                // we'll push all versePromises into an array…
                var versePromise = self.getVerse(value.surah, value.ayah).then(function (res) {
                    // the return value of this `then` modifies `versePromise`
                    return {
                        surah: value.surah,
                        ayah: value.ayah,
                        text: res
                    };
                });
                versePromises.push(versePromise);
            });

            return $q.all(versePromises); // modifies $http promise — this is our ultimate promised value
            // if a versePromise fails, $q.all will fail; add a `catch` when using getAyahsByJuz!
        });
    }, 30);

}

However, there is still a huge issue here… why aren't you using the server response of your $http call anywhere? What is the point of that first call?

Also I find that $timeout to be extremely suspicious. If you need it then it's likely there's something bad going on elsewhere in the code.

Upvotes: 3

Related Questions