Asynchronous programming with promises
Grégoire Charvet
greg@geekingfrog.com
asynchroWhat ?
- Start something, which will complete sometime in the future
- One thread: cannot block it
Ajax: Asynchronous javascript and XML
How to manage that ?
3 methods
Callbacks
With jquery in the browser
$("#loading-sign").hide(500, function elementHidden() {
$("#ok-sign").show(); // executed when the #loading-sign is hidden, after 500ms});
With node (node.js style callback)
require('fs').readdir('.', function(err, files){
if(err) {
console.error(err);
} else {
console.log("there are "+files.length+" objects in the current directory");
}
});
Callbacks (2)
$.getJSON("/api/get_recent_news", function gotNews(news) {
console.log("got "+news.length+" news");
}, function rejected(xhr){
console.error("request failed because "+xhr.responseText);
});
Events
Emit an event when the action is completed (or failed)
var server = require('http').createServer();
server.on('request', function onRequest(request, response) {
response.writeHead(200, {"Content-Type": "text/plain"});
response.write("Pong");
response.end();
}
server.listen(8080);
Promise
With jquery
var getNews = $.getJSON("/api/get_latest_news"); //ajax call
getNews.done(function gotNews(news){ //invoked when the server responds
console.log("There are "+news.length+" recent news");});
In node: Q module by Kriskowal.
What is a promise ?
- Read only object which have a state and a value.
- Keep track of its state and value.
- $.ajax returns a promise.
Monitor the states
- promise.done(fn)
- promise.fail(fn)
- promise.always(fn)
Creating a promise (1)
- $.ajax(), $.getJSON(), $.post()
- $.Deferred().promise()
Creating a promise (2)
var createPromise = function() {
var deferred = $.Deferred();
setTimeout(function resolver(){
deferred.resolve("Hello");
}, 1000);
return deferred.promise();
}
var promise = createPromise()
promise.done(function promiseResolved(message) {
console.log("got message: "+message);
});
console.log("all done");
Output ?
Creating a promise (3)
all done
// 1 second later
got message: Hello
Re-use a promise
setTimeout(function addCallback() {
console.log("adding a callback");
promise.done(function{ //promise is already resolved here
$("#ok").show();
console.log("show element");
});
console.log("callback added")
, 5000);
Output ?
adding a callback
show element
callback added
"real life" example with promises
building a news feed
- User login -> response:
{
"user_id": "123",
"feeds_ids": [
2, 3, 5
]
}
- Fetch recent news for the returned feeds:
$.getJSON("/api/feeds/2/latest")
- Display the total of recent articles
Transform a promise
Using the then method on a promise to return another value
promise.then(function transform(value){
//transform value here and return a new value
})
value = $.Deferred(function resolver(d){ d.resolve(4); }).promise();
squared = value.then(function(val) { return val*val; });
squared.done(function(sq) {
console.log("squared value: "+ sq);
});
Wait for multiple promises
$.when(p1, p2, ... pn) returns a new promise which will resolve the values of the resolved promises.
$.when(p1,p2,p3).done(function allDone(v1,v2,v3) {
// All promises have been resolved
// do something with v1, v2 and v3
});
$.when(p1,p2,p3).fail(function rejected(reason) {
// one of the the promise was rejected
});
Back to the news feed example
- Authenticate the user
- Get all the feeds' ids
- Retrieve all latest articles from the feeds
- Compute the total number of articles
Step 1: authenticate the user
var login = $.post("/login", {username: "Twilight", password: "Sparkle"})
Step 2
: get the feeds' ids
response to login:
{
"user_id": "123",
"feeds_ids": [
2, 3, 5
]
}
var feedsIds = login.then(function(response) {
return response.feeds_ids;
});
Step 3: retrieve the latest
var articles = feedsIds.then(function gatherArticles(ids) {
return ids.map(function getArticles(id) {
return $.getJSON("/api/feeds/"+id+"/latest");
});
});
//articles is an array of promises
[
promise for getJSON(/api/feeds/1/latest),
promise for getJSON(/api/feeds/2/latest),
promise for getJSON(/api/feeds/3/latest)
]
Step 4: compute the total number of articles
var allArticles = $.when.apply($, articles);
var total = allArticles.then(function computeTotal() {
var articles = [].slice.call(arguments);
return articles.reduce(function sum(acc, val) {
return acc + val.length;
},0);
});
Step 5: do something with it
total.done(function displayTotal(total) {
target = document.getElementById("#totalArticles");
totalDiv = document.createElement('div');
totalDiv.textContent = "Grand total of "+total+" articles";
target.appendChild(totalDiv);
});
Comparison
Callbacks based vs promise based vs event based
Callbacks +
- simple
- easy to understand
- traceable
Callbacks -
- difficult to pass data
- difficult to change
- callback hell
Promises +
- read only
- easy to change the flow
- powerful way to manage complex data dependencies
Promises -
- harder to understand
- not as widespread as callbacks
- can be tricky to debug
- (slow)
Events +
- Unexpected stuff
- Message oriented
Events -
- Be careful with memory management
- Can be hard to debug
Pretty different usage from the two others techniques.
What to remember ?
- With promise, one describes the dependency between data.
- With callbacks, one describes the flow to generated the data.
- Promises are a powerful and flexible abstraction.
More stuff
- For node.js : Q module.
- JQuery documentation: jQuery Deferred object
- Interesting post about the difference between callbacks style and promise style (focus on node.js)
- In ES6, new way to manage asynchronous control with generator (yield keyword). See: Promise with generators and generators with callbacks.
Questions ?
This presentation:
http://slid.es/geekingfrog/asynchronous-programming-with-promises
greg@geekingfrog.com
Asynchronous programming with promises
By geekingfrog
Asynchronous programming with promises
- 2,870