Let's compare the promise example to a pure Javascript example:
// First we need a convenience function for W3C's fiddly XMLHttpRequest.
// It works a little differently from the promise framework. Instead of
// returning a promise to which we can attach a handler later with .then(),
// the function accepts the handler function as an argument named 'callback'.
function requestSomeDataAndCall(url, callback) {
var req = new XMLHttpRequest();
req.onreadystatechange = resHandler;
req.open("GET", url, false);
req.send();
function resHandler() {
if (this.readyState==4 && this.status==200) {
callback(this);
} else {
// todo: Handle error.
}
}
}
requestSomeDataAndCall("http://example.com/foo", function(res){
setTimeout(function(){
var data = JSON.parse(res.responseText);
setTimeout(function(){
var price = data.price;
setTimeout(function(){
print("The price is "+price);
},10);
},10);
},10);
});
As Norbert Hartl pointed out, JSON.parse() will hang the browser for large strings. So I used setTimeout() to delay its execution (after a pause of 10 milliseconds). This is one example of Kris Kowal's solution. It allows the current Javascript thread to complete, freeing up the browser to present DOM changes and scroll the page for the user, before the callback runs.
I hope the commonjs promise framework also uses something like setTimeout, otherwise the later promises in the article's example will indeed run synchronously as feared.
My alternative above looks pretty ugly, with the later processes requiring further indentation. I restructured the code, so that we can provide our process chain all in one level:
function makeResolver(chain) {
function climbChain(input) {
var fn = chain.shift(); // This particular implementation
setTimeout(function(){ // alters the chain array.
var output = fn(input);
if (chain.length>0) {
climbChain(output);
}
},10);
}
return climbChain;
}
var processChain = [
function(response){
return JSON.parse(response.body);
},
function(data){
return data.price; // get the price
},
function(price){
print("The price is " + price);
}
];
var climber = makeResolver(promiseChain);
requestSomeDataAndCall("http://example.com/foo", climber);
I was hoping to demonstrate that traditional forward-passing of callbacks in Javascript is pretty much equivalent to promises. However after two attempts I appear to have shown, with reference to the neatness of the code in the original example, that promises are a far more elegant solution!