Load images with jQuery Deferred
Have you ever needed to know the dimensions of an image in JavaScript? In the past, you may have used code like this:
var image = new Image(); image.onload = function() { alert("Loaded image: " + image.width + "x" + image.height); }; image.src = "/my-image.png";
This is okay for very basic scenarios, but it becomes tricky to handle things like:
- Notifying more than one part of your application when the image is loaded
- Error handling
- Loading multiple images in parallel
- Chaining together a sequence of asynchronous operations
I also dislike the backwards nature of writing the callback function before
assigning the src
property. This is essential however, because
image loading is asynchronous and could potentially finish before your callback was
assigned to onload
.
Fortunately, there is a cleaner way to handle this.
Using jQuery Deferred
The jQuery deferred object is perfect for wrapping up asynchronous operations, such as image loading. The deferred object provides a consistent API for adding callbacks and chaining operations. It results in very readable code.
Here's what it's like to load an image using a jQuery deferred object.
$.loadImage("/my-image.png") .done(function(image) { alert("Loaded image: " + image.width + "x" + image.height); }) .fail(function(image) { alert("Failed to load image"); });
jQuery deferred's are easy to chain together using the pipe
function.
For example, imagine we need the dimensions of a user's avatar image.
But first we need to the image URL from a profile retrieved via ajax...
var avatarDimensions = $.get("/user-profile") .pipe(function(profile) { return $.loadImage(profile.avatarUrl); }) .pipe(function(avatarImage) { return { width: avatarImage.width, height: avatarImage.height }; }); // avatarDimensions is a deferred object. avatarDimensions.done(function(dimensions) { console.log(dimensions); });
Implementing $.loadImage
Here's the code for $.loadImage
.
It assumes you have already included jQuery 1.5 or newer in your page.
$.loadImage = function(url) { // Define a "worker" function that should eventually resolve or reject the deferred object. var loadImage = function(deferred) { var image = new Image(); // Set up event handlers to know when the image has loaded // or fails to load due to an error or abort. image.onload = loaded; image.onerror = errored; // URL returns 404, etc image.onabort = errored; // IE may call this if user clicks "Stop" // Setting the src property begins loading the image. image.src = url; function loaded() { unbindEvents(); // Calling resolve means the image loaded sucessfully and is ready to use. deferred.resolve(image); } function errored() { unbindEvents(); // Calling reject means we failed to load the image (e.g. 404, server offline, etc). deferred.reject(image); } function unbindEvents() { // Ensures the event callbacks only get called once. image.onload = null; image.onerror = null; image.onabort = null; } }; // Create the deferred object that will contain the loaded image. // We don't want callers to have access to the resolve() and reject() methods, // so convert to "read-only" by calling `promise()`. return $.Deferred(loadImage).promise(); };