Skip to content
Brian Cavalier edited this page May 10, 2014 · 16 revisions

when.defer()

You can use when.defer() to create a deferred object that has a promise for a value that will become available at some point in the future.

function loadImage (src) {
	var deferred = when.defer(),
		img = document.createElement('img');
	img.onload = function () { 
		deferred.resolve(img); 
	};
	img.onerror = function () { 
		deferred.reject(new Error('Image not found: ' + src));
	};
	img.src = src;

	// Return only the promise, so that the caller cannot
	// resolve, reject, or otherwise muck with the original deferred.
	return deferred.promise;
}

// example usage:
loadImage('http://google.com/favicon.ico').then(
	function gotIt(img) {
		document.body.appendChild(img);
                return img; 
	},
	function doh(err) {
		document.body.appendChild(document.createTextNode(err));
	}
).then(
	function shout(img) {
		alert('see my new ' + img.src + '?');
	}
);

when.all()

Using the same loadImage function, we can load multiple images and return a promise that resolves only when all images have loaded.

function loadImages(srcs) {
	// srcs = array of image src urls

	// Array to hold deferred for each image being loaded
	var deferreds = [];

	// Call loadImage for each src, and push the returned deferred
	// onto the deferreds array
	for(var i = 0, len = srcs.length; i < len; i++) {
		deferreds.push(loadImage(srcs[i]));
		
		// NOTE: We could push only the promise, but since this array never
		// leaves the loadImages function, it's ok to push the whole
		// deferred.  No one can gain access to them.
		// However, if this array were exposed (e.g. via return value),
		// it would be better to push only the promise.
	}

	// Return a new promise that will resolve only when all the
	// promises in deferreds have resolved.
	// NOTE: when.all returns only a promise, not a deferred, so
	// this is safe to expose to the caller.
	return when.all(deferreds);
}

Then do something fancy after the images are loaded

loadImages(imageSrcArray).then(
	function gotEm(imageArray) {
		doFancyStuffWithImages(imageArray);
		return imageArray.length;
	},
	function doh(err) {
		handleError(err);
	}
).then(
    function shout (count) {
		// This will happen after gotEm() and count is the value
		// returned by gotEm()
        alert('see my new ' + count + ' images?');
    }
);

Or, equivalently using when() instead of .then()

when(loadImages(imageSrcArray),
	function gotEm(imageArray) {
		doFancyStuffWithImages(imageArray);
		return imageArray.length;
	},
	function doh(err) {
		handleError(err);
	}
).then(
    function shout (count) {
		// This will happen after gotEm() and count is the value
		// returned by gotEm()
        alert('see my new ' + count + ' images?');
    }
);

when.any()

Let's say we have an image rotator widget that displays a series of images, one at a time. Here is a revised implementation of the loadImages function from above, which now returns the array of promises it creates.

function loadImages(srcs) {
    // srcs = array of image src urls

    // Array to hold deferred for each image being loaded
    var promises = [];

    // Call loadImage for each src, and push the returned deferred
    // onto the deferreds array
    for(var i = 0, len = srcs.length; i < len; i++) {
		// Note that loadImage() returns a promise, not the entire
		// deferred
        promises.push(loadImage(srcs[i]));

    }

	// Return the array of promises
    return promises;
}

When our image rotator starts up (or even beforehand, possibly on pageload, for example), we could fire off requests for all the images, but only care about the one that loads first (i.e. fastest).

This has the benefit of pre-loading and letting the browser cache the remaining images, without any additional work, so when we need to display them later, they are fast.

// Load a bunch of images that we will display in an image rotator.
// Only care about the first one that loads, so we can show it to the
// the user ASAP.
// This has the benefit of pre-loading and letting the browser cache the
// remaining images, without any additional work.
when.any(loadImages(imageSrcArray),
	function(firstAvailableImage) {
		// In when.js >= 0.10.0
		//   firstAvailableImage will be the actual Image that caused
		//   when.any() to complete
		imageRotator.showImage(firstAvailableImage);

		// NOTE: In when.js <= 0.9.4:
		//   firstAvailableImage will be an array with 1 Image in it 
		// imageRotator.showImage(firstAvailableImage[0]);		
	}
);

when.some()

Now let's say we have a more advanced image rotator that can display 3 images to the user at once.

// Load a bunch of images that we will display in an image rotator.
// Only care about the first 3, so we can show them to the user ASAP.
// Again, this will also pre-load and allow the browser to cache the
// remaining images.
when.some(loadImages(imageSrcArray), 3,
	function(initialImageSet) {
		// initialImageSet will be an array of the first 3 (or 
		// imageSrcArray.length, if < 3) Images that loaded
		imageRotator.showImages(initialImageSet);
	
	}
);

when.map()

Similar to Array.prototype.map(), when.map() lets you transform an array of values, but it allows the input array to contain promises or values, and the map function you provide can return either a promise or value. when.map() returns a promise that will resolve to the fully mapped array of actual result values.

So, we can use when.map() to implement a more compact version of the when.all() loadImages example above. Notice that we can use the loadImage function directly as the mapping function, and that when.map() does the work of the loadImages (plural) for us.

when.map(srcs, loadImage).then(
	function gotEm(imageArray) {
		doFancyStuffWithImages(imageArray);
		return imageArray.length;
	},
	function doh(err) {
		handleError(err);
	}
).then(
    function shout (count) {
		// This will happen after gotEm() and count is the value
		// returned by gotEm()
        alert('see my new ' + count + ' images?');
    }
);

when.reduce()

You guessed it, similar to Array.prototype.reduce(), when.reduce() performs a reduce on an array of promises or values. The reduce function you provide can return either a promise or value. when.reduce returns a promise that resolves to the final reduce value.

Let's say we not only want to load a bunch of images, but we want to composite them onto a canvas. Since when.reduce() will reduce in strict left-to-right order, we can count on compositing the images in the desired order:

function compositeImageOntoCanvas
var canvas;
// canvas = html canvas onto which we'll composite the images
// srcs = array of image src urls
when.reduce(srcs, function(canvas, src) {
	return when(loadImage(src), function(image) {
		compositeImageOntoCanvas(canvas, image);
		return canvas;
	});
}, canvas).then(
	function showCanvas(canvas) {
		// show canvas in html page
	},
	function doh(err) {
		handleError(err);
	}
);