"done" vs. "then" and a design pattern
Note:
Async programming for Ajax calls is a very common scenario, so this article will use Ajax as example in using Promise.
Comparing to parallel calls, make sequence call with promise are more complex and confusing.
jQuery2.0 $.when() Is not a Bad Choice
Many articles talk about the jQuery's implementation of promise is inadequate and is not compliant with Promise/A proposal, however
a. With my test of jQuery 2.0, I didn't notice the non-compliant issue mentioned in http://blog.mediumequalsmessage.com/promise-deferred-objects-in-javascript-pt1-theory-and-semantics
b. According to this link, the performance of jQuery promise is even better than when.js http://complexitymaze.com/2014/03/03/javascript-promises-a-comparison-of-libraries/
c. Other promise library like when.js is not documented better than jQuery and its usage for browser requires the hassle to just run a build for browser version.
d. jQuery promise should meet most of the requirements for development for browser such as parallel loading, timeout, and sequence loading ...
done() vs. then()
When chaining multiple promises together, using done() is very different from then() even though both are executed after the promise is resolved. They serve very different purposes.
Basically to chain multiple promises as promise 1 -> promise 2 -> promise 3 ... in sequence, we have to use then to RETURN new promise to chain them together in "sequence".
If we just use "done()" or not RETURN a new promise from then(), then all calls will be executed immediately after the first promise 1 is resolved in the sequence of normal code execution which is a total miss of the point of promise.
In other words, in stead of
promise 1 -> promise 2 -> promise 3,
the execution will be
promise 1 ->
promise 2, promise 3, promise 4 ...
promise 2, 3, 4 ... will only wait promise 1 and will executed all together without promise 4 wait for promise 3, and promise 3 wait for promise 2 ...
This is the complete code, pay attention to the comments
(function() {
$(function () {
var url1 = "http://api.census.gov/data/2013/pep/natstprc";
var url2 = "http://api.census.gov/data/2013/pep/natstprc18";
var url3 = "http://api.census.gov/data/2013/pep/monthlynatchar5";
$.when($.ajax(url1)) // working with promise 1
.done(function(data){ // working with promise 1, executed when promise 1 is available
$('.myContent').append('<div>promise 1: ' + data[0].title + '</div>');
})
.fail(function(error){ // working with promise 1, executed when promise 1 fails
$('.myContent').append('<div>promise 1: ' + error.statusText + '</div>');
})
.then(
function(data) { // still working with promise 1, entering the promise 2 by returning promise 2
// use data to with the result of promise 1
// must return a new promise
return $.ajax(url2);
},
function()
{
return $.ajax(url2); // to tolerate the failure of promise 1 and continue to promise 2
}
)
.done(function(data){ // working with promise 2, if the first then doesn't return a new promise then here we will still work with promise 1
$('.myContent').append('<div>promise 2: ' + data[0].title + '</div>');
})
.fail(function(error){ // working with promise 2, if the first then doesn't return a new promise then here we will still work with promise 1
$('.myContent').append('<div>promise 2: ' + error.statusText + '</div>');
})
.then(
function(){ // working with promise 2, entering the promise 3 by returning promise 3
return $.ajax(url3);
},
function() // to tolerate the failure of promise 2 and continue to promise 3
{
return $.ajax(url3);
}
)
.done(function(data){ // working with promise 3
$('.myContent').append('<div>promise 3: ' + data[0].title + '</div>');
})
.fail(function(error){ // working with promise 3
$('.myContent').append('<div>promise 3: ' + error.statusText + '</div>');
}); // add more then if necessary
})
}());
Run the code, you will see the results shows in a sequence order.
promise 1: Population, Total - United States Of America
promise 2: United States: Population, female (% of total)
promise 3: United States: Population density (people per sq. km of land area)
To test the error handling, mess up the url2 and you will have result like
promise 1: Population, Total - United States Of America
promise 2: Not Found
promise 3: United States: Population density (people per sq. km of land area)
.then(
function(data) { // still working with promise 1, entering the promise 2 by returning promise 2
// use data to with the result of promise 1
// must return a new promise
return $.ajax(url2);
},
function()
{
return $.ajax(url2); // to tolerate the failure of promise 1 and continue to promise 2
}
)
Notice that the above pattern tolerates the error from the promise 2 by returning a new promise in the "fail" function inside the "then()".
The readability of this code does not seem to much improvement over just using nested callbacks, but it helps to explain several pitfalls, such as using something like
.then(
function(data) {
$.ajax(url2);
},
},
or
.then(
function(data) {
myAjaxcall();
},
},
with which they fail in returning a new promise to make the chained sequence call happen.
Rewrite the above code using named functions instead anonymous functions will make the pattern much more clear and address the point of using promise.
(function() { var url1 = "http://api.census.gov/data/2013/pep/natstprc"; var url2 = "http://api.census.gov/data/2013/pep/natstprc18"; var url3 = "http://api.census.gov/data/2013/pep/monthlynatchar5"; function getData1() { return $.ajax(url1); } function getData2() { return $.ajax(url2); } function getData3() { return $.ajax(url3); } function showData1(data) { var txt = data[0].title? data[0].title: '' + data.statusText? data.statusText: ''; $('.myContent').append('<div>promise 1: ' + txt + '</div>'); } function showData2(data) { var txt = data[0].title? data[0].title: '' + data.statusText? data.statusText: ''; $('.myContent').append('<div>promise 2: ' + txt + '</div>'); } function showData3(data) { var txt = data[0].title? data[0].title: '' + data.statusText? data.statusText: ''; $('.myContent').append('<div>promise 3: ' + txt + '</div>'); } $(function () { $.when(getData1()) // start of promise 1 .done(showData1) .fail(showData1) .then(getData2, getData2) // start of promise 2 and tolerate error from promise 1 .done(showData2) .fail(showData2) .then(getData3, getData3) // start of promise 3 and tolerate error from promise 3 .done(showData3) .fail(showData3); }) }());
As you can see the chained promise pattern is much more clear.
References:
https://gist.github.com/domenic/3889970
http://blog.mediumequalsmessage.com/promise-deferred-objects-in-javascript-pt1-theory-and-semantics
http://blog.mediumequalsmessage.com/promise-deferred-objects-in-javascript-pt2-practical-use
http://tutorials.jenkov.com/jquery/deferred-objects.html (point is missing)
http://www.danieldemmel.me/blog/2013/03/22/an-introduction-to-jquery-deferred-slash-promise/
http://www.html5rocks.com/en/tutorials/es6/promises/
https://thewayofcode.wordpress.com/tag/jquery-deferred-broken/
http://www.maori.geek.nz/post/i_promise_this_will_be_short
No comments:
Post a Comment