Resolve promise when all images are loaded

11,447

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);
});
Share:
11,447
U r s u s
Author by

U r s u s

Updated on June 25, 2022

Comments

  • U r s u s
    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:

    1. what's happening in each function;
    2. why I need $q.all to make sure all images are loaded before resolving the promises.

    The relevant docs are somewhat cryptic.