Fabio Marzocca
Fabio Marzocca

Reputation: 1677

Wait for images to load after creating them via an AJAX request

I am getting images (74 files) with an AJAX request. I am loading them in to an array. Then I change some style calling the function changeMargins(). The issue is that the function is called when the images are not yet all loaded and ready.

imageArray = new Array();     

$.ajax({
  url: url,
  success: function (data) {
    counter = 0
    $(data).find("a").attr("href", function (i, val) {
      if (val.match(/\.(jpe?g|png|GIF)$/)) { 
        imageArray[counter]= new imageItem(url + val)
        ++counter;  
      }
    });

    changeMargins(imageArray);

How can I wait for completion of the AJAX and all load events on the Image elements and then continue processing?

Upvotes: 0

Views: 183

Answers (1)

Roamer-1888
Roamer-1888

Reputation: 19288

This problem is probably best addressed by "promisifying" the image loading process, ie create a promise that resolves when the images have loaded.

There's a design decision to be made ... whether (a) to swallow load errors or (b) to allow any individual error to cause the whole process to fail.

Assuming (a), you would write something like this :

function loadImages() {
    return $.ajax({
        url: url,
        // other ajax params?
    }).then(function(data) {
        return $.Deferred(function(dfrd) { // promisify the entire image loading proceess
            var imageArray = [];
            var counter = 0; // tally of images that have either loaded or failed to load
            $(data).find('a').get().map(function(element) {
                // map a elements in data to an array of their hrefs
                return element.href;
            }).filter(function (href) {
                // filter out any non-jpe?g|png|GIF 
                return href.match(/\.(jpe?g|png|GIF)$/);
            }).forEach(function(val) {
                // for each match, create a Image() object, push onto the array, attach onload and onerror handlers, and set the `src` attribute.
                var img = new Image();
                imageArray.push(img);
                img.onload = function loadHandler() {
                    counter += 1;
                    if(counter == images.length) {
                        dfrd.resolve(imageArray);
                    }
                };
                img.onerror = function errorHandler() {
                    console.log(new Error('image ' + url + val + ' failed to load'));
                    // Here, you might choose to splice failed Images out of `imageArray`.
                    counter += 1;
                    if(counter == images.length) {
                        dfrd.resolve(imageArray);
                    }
                };
                img.src = url + val;
            });
        });
    }).then(function(imageArray) {
        changeMargins(imageArray);
        return imageArray;
    });
}

Notes:

  • More typically, one would choose to promisify at the lowest level (ie each Image() individually) but promises are fairly expensive, therefore with 74 images, it's better to promisify en masse.
  • By promisifying, and returning a promise from loadImages(), its caller is informed of completion of the process - you can chain loadImages().then(...) and do stuff when the images have loaded/failed.

Upvotes: 1

Related Questions