Thanks!

Dr. David Rodenas / @drpicox / david-rodenas.com

Introduction to Promises

put order in your callbacks mess

Dr. David Rodenas
@drpicox

My first day in NodeJS

SOURCE CODE
MY OBJECTIVE

function render_page(req, res) {
  req.facebook.app(function(err, app) {
    req.facebook.me(function(user) {
      res.render('index.ejs', {
        layout:    false,
        req:       req,
        app:       app,
        user:      user
      });
    });
  });
}
  

Ok, two nested functions just to get data.

My mind does something like...


function render_page(req, res) {
  app  = req.facebook.app(function(err, app) {
  user = req.facebook.me(function(user) {
  res.render('index.ejs', {
        layout:    false,
        req:       req,
        app:       app,
        user:      user
  });
  });
  });
}
  

note that app and user could be obtained in parallel (concurrently)

But... same file some lines later...


function handle_facebook_request(req, res) {

  // if the user is logged in
  if (req.facebook.token) {

    async.parallel([
      function(cb) {
        // query 4 friends and send them to the socket for this socket id
        req.facebook.get('/me/friends', { limit: 4 }, function(friends) {
          req.friends = friends;
          cb();
        });
      },
      ...
      function(cb) {
        // use fql to get a list of my friends that are using this app
        req.facebook.fql('SELECT uid, name, is_app_user, pic_square FROM user WHERE uid in (SELECT uid2 FROM friend WHERE uid1 = me()) AND is_app_user = 1', function(result) {
          req.friends_using_app = result;
          cb();
        });
      }
    ], function() {
      render_page(req, res);
    });

  } else {
    render_page(req, res);
  }
}
WTF!!!

I wanted to include eltanin-eye
(a Facebook profile based recommender)


Send info to eltanin:


function handle_facebook_request_eltanin(req, res, me) {
  eltanin.w('users').w(me).w('social/fb').put(
      {fb_id:me, token:req.facebook.token}
      ).on('complete', function() {
        handle_facebook_request(req, res, me);
  });    
}
  

More info to render:


function handle_facebook_request(req, res, me) {

  // if the user is logged in
  if (req.facebook.token) {

    async.parallel([
    ...
      function(cb) {
        // = request product recommendations of the current person
        eltanin.w('users').w(me).w('recommendations')
            .q('limit',50).get().on('complete', function(data) {
          req.recommendations = data;
          cb();
        });
      },
    ...
    ], function() {
      render_page(req, res);
    });

  } else {
    render_page(req, res);
  }

}
  

But...
when I finished... and I get it running... running fast...
my feeling was...

My reflexion about JS

PROBLEMS
DESIRES

Explicit parallelism

  • When to wait?
  • When to be concurrent?
  • We have the best concurrency?

async.parallel([
    function(cb) {
      ...
    }, ...
]);

async.series([
    function(cb) {
      ...
    }, ...
]);
    

Error prone

  • What if I forget a cb()?
  • What about exceptions?
  • What if we mistake parellelism?
    (very difficult to debug)

function getUsersLikes(cb) {
  $get("/users", function (users) {
    var result = 0, finisheds = 0;
    function finished() {
      if (++finisheds===users.length){
        cb(result);
    } }
    users.forEach(function (user) {
      $get("/users/"+user+"/likes", 
          function(likes) {
        result += likes; 
        finished();
  });});});
}
    

Software Engineering

  • Data can be reused quickly?
  • Complex method signatures?
  • Is there a chain of responsability?
  • It's easily composable?

Callbacks are good for events,
but to get data are not suitable.


function render_page(req, res) {
  req.facebook.app(function(err,app){
    req.facebook.me(function(user) {
      res.render('index.ejs', {
        layout:    false,
        req:       req,
        app:       app,
        user:      user
      });
    });
  });
}
  

Code like Always


function render_page(req, res) {
  var app  = req.facebook.app();
  var user = req.facebook.me();
  res.render('index.ejs', {
        layout:    false,
        req:       req,
        app:       app,
        user:      user
  });
}
   

Error handling


function handle_request(req, res) {
  try {
    render_page(req, res);
  } catch(err) {
    handle_error(err);
  }
}
    

Implicit parallelism


function render_page(req, res) {
  var app  = req.facebook.app(); // start getting app
  var user = req.facebook.me();  // start getting user
  res.render('index.ejs', {
        layout:    false,
        req:       req,
        app:       app,      // uses app, waits for app
        user:      user      // uses user, waits for user
  });
}
    

Un-inverts the chain of responsability:

instead of calling a passed callback, returns a promise.


      getWithCallback(resource, function (result) {
        doSomething(result);
      });
    

      var promise = getWithPromise(resource);
      promise.then(function (result) {
        doSomething(result);
      });
    

It seems simple, right? May be too simple to be useful? Let's see an example.


function render_page(req, res) {
  var app = getFacebookApp();
  var me  = getFacebookMe();
  Q.all([app, me], function (results) {
    res.render('index.ejs', {
        layout:    false,
        req:       req,
        app:       results[0],  // apps
        user:      results[1]   // me
    });    
  });
}
  

Implicit concurrency, easy composibility, less prone to bugs, code almost like always.

Promises Syntax

Create a promise


function getFacebookApp() {
  var deferred = Q.defer();
  req.facebook.app(function(err, app) {
    if (err) {
      deferred.reject(err);
    } else {
      deferred.resolve(app);
    }
  });
  return deferred.promise;
}
// there are lots of already coded wappers
    

Chain promises

      
var user = getUserWithPromise(me);
var name = user.then(function (user) {
  return user.name;
});
var length = name.then(function (name) {
  return name.length;
});
// each then returns a new promise
    

Compose promises


var app = getFacebookApp();
var me  = getFacebookMe();
var appAndMe = Q.all([app, me]);
meAndApp.then(function (appAndMe) {
  doSomething(appAndMe);
});
// check for .spread instead of then
    

Exception bubbling


var user = getUserWithPromise(me);
var name = user.then(function (user) {
  return user.name;
});
name.then(function(name) {
  doSomething(name);
}).catch(function(err) {
  // also triggered if user fails
  handle(err);
}).finally(function() {
  tidyUp();
});
    

Resources

Q
Kris Kowal promises

$q
AngularJS promises

  • http://docs.angularjs.org/api/ng.$q
  • Inspired in Kris Kowal promises
  • Implements minimum expected functionalities
  • Fully integrated in AngularJS expressions:
    • you can use them directly in expressions {{promise}}
    • view loading synchronizes with promises