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

  1. Authenticate the user
  2. Get all the feeds' ids
  3. Retrieve all latest articles from the feeds
  4. 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


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,859