Hi,
I’ll post in English this time in order to have a larger audience, hopefully. So let’s begin…
These last weeks I have been writing asynchronous javascript code for a full javascript application. As the application grows so does code complexity, then in order to keep your code clean and easy to maintain, you will somehow have to refactor it or else you’ll have to deal with « callback spaghetti » code. At that point, a good knowledge of jQuery promises and the following tricks might be usefull.
Let’s say you have a random number of promises you want to synchronise : like getting all the results before to do something else. Thanks to Herman Radtke, you can do something like :
function par(array) { var deferred = $.Deferred(); var fulfilled = 0, length = array.length; var results = []; if (array && array.length == 0) { deferred.resolve(results); } else { array.forEach(function(promise, i) { $.when(promise()).then(function(value) { results[i] = value; fulfilled++; if (fulfilled === length) { deferred.resolve(results); } }); }); } return deferred.promise(); }
Given this kind of input, here is how to use it :
var promises = function() { var promises = []; for (var i = 0; i < 5; i++) { promises.push(function() { return $.Deferred( function(dfd) { // Whatever task dfd.resolve("my result"); }).promise(); }); } return promises; }; $.when(par(proms)).then(function(results) { assert(results.length == proms.length, "Ok"); });
Now in another use case, you want the promises to be executed in sequence and get the results in correct order. So in the same idea we can write :
function seq(array) { var seed = $.Deferred(), result = $.Deferred(), finalPromise; var results = []; finalPromise = array.reduce(function(promise, request) { return promise.pipe(function(input) { if (input) results.push(input); return request(); }); }, seed.promise()); seed.resolve(); finalPromise.done( function(i) { results.push(i); }).then(function() { result.resolve(results); }); return result.promise(); }
Thanks to my mate Sadek Drobi, here is a much better version of the two previous functions, in a very elegant functional style:
function seq(array) { var seed = $.Deferred().resolve([]); if (!array || array.length == 0) return seed; else { return array.reduce(function(state, promise) { return state.pipe(function(input) { return promise().pipe(function(i) { return input.concat(i); }); }); }, seed.promise()); } } function par(array) { var seed = $.Deferred().resolve([]); if (!array || array.length == 0) return seed; else { return array.map( function(p) { return p(); }).reduce(function(state, promise) { return state.pipe(function(input) { return promise.pipe(function(i) { return input.concat(i); }); }); }, seed.promise()); } }
In order to test that everything gets executed in the right order, I have written a very little node.js server script that waits a few seconds before responding to a request. You can find the whole code on github.
Constructive comments are highly appreciated.
Cheers.