Resolve promise when all images are loaded
Solution 1
Check out this plunkr.
Your function:
function preLoad() {
var promises = [];
function loadImage(src) {
return $q(function(resolve,reject) {
var image = new Image();
image.src = src;
image.onload = function() {
console.log("loaded image: "+src);
resolve(image);
};
image.onerror = function(e) {
reject(e);
};
})
}
$scope.images.forEach(function(src) {
promises.push(loadImage(src));
})
return $q.all(promises).then(function(results) {
console.log('promises array all resolved');
$scope.results = results;
return results;
});
}
The idea is very similar to Henrique's answer, but the onload handler is used to resolve each promise, and onerror is used to reject each promise.
To answer your questions:
1) Promise factory
$q(function(resolve,reject) { ... })
constructs a Promise. Whatever is passed to the resolve
function will be used in the then
function. For example:
$q(function(resolve,reject) {
if (Math.floor(Math.random() * 10) > 4) {
resolve("success")
}
else {
reject("failure")
}
}.then(function wasResolved(result) {
console.log(result) // "success"
}, function wasRejected(error) {
console.log(error) // "failure"
})
2) $q.all is passed an array of promises, then
takes a function which is passed an array with the resolutions of all the original promises.
Solution 2
I'm not used to angular promise library, but the idea is as follows:
function getImagePromise(imgData) {
var imgEl = new Image();
imgEl.src = imgData.imgPath;
return $q(function(resolve, reject){
imgEl.addEventListener('load', function(){
if ((
'naturalHeight' in this
&& this.naturalHeight + this.naturalWidth === 0
)
|| (this.width + this.height == 0)) {
reject(new Error('Image not loaded:' + this.src));
} else {
resolve(this);
}
});
imgEl.addEventListener('error', function(){
reject(new Error('Image not loaded:' + this.src));
});
})
}
function preLoad() {
return $q.all($scope.abbreviations.map(getImagePromise));
}
// using
preLoad().then(function(data){
console.log("Loaded successfully");
data.map(console.log, console);
}, function(reason){
console.error("Error loading: " + reason);
});
U r s u s
Updated on June 25, 2022Comments
-
U r s u s almost 2 years
I was preloading images with the following code:
function preLoad() { var deferred = $q.defer(); var imageArray = []; for (var i = 0; i < $scope.abbreviations.length; i++) { imageArray[i] = new Image(); imageArray[i].src = $scope.abbreviations[i].imgPath; } imageArray.forEach.onload = function () { deferred.resolve(); console.log('Resolved'); } imageArray.forEach.onerror = function () { deferred.reject(); console.log('Rejected') } return deferred.promise; } preLoad();
I thought images were all loading correctly because I could see the 'Resolved' log.
Later somebody pointed out that the code above doesn't guarantee that all images are loaded before resolving the promise. In fact, only the first promise is resolved.
I was advised to use
$q.all
applied to an array of promises instead. This is the resulting code:function preLoad() { var imageArray = []; var promises; for (var i = 0; i < $scope.abbreviations.length; i++) { imageArray[i] = new Image(); imageArray[i].src = $scope.abbreviations[i].imgPath; }; function resolvePromises(n) { return $q.when(n); } promises = imageArray.map(resolvePromises); $q.all(promises).then(function (results) { console.log('array promises resolved with', results); }); } preLoad();
This works, but I want to understand:
- what's happening in each function;
- why I need
$q.all
to make sure all images are loaded before resolving the promises.
The relevant docs are somewhat cryptic.