Reputation: 2785
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
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