Sane Async Patterns
       Trevor Burnham
     HTML5DevConf 2013
http://dev.hubspot.com/jobs
https://pragprog.com/books/tbcoffee
https://pragprog.com/books/tbajs
https://leanpub.com/npm
In This Talk

• Callback arguments considered harmful
• Three alternative patterns:
  • PubSub
  • Promises
  • AMD
The Callback Argument
     Anti-Pattern
Pyramid of Doom
mainWindow.menu("File", function(err, file) {
  if(err) throw err;
  file.openMenu(function(err, menu) {
    if(err) throw err;
    menu.item("Open", function(err, item) {
      if(err) throw err;
      item.click(function(err) {
        if(err) throw err;
        window.createDialog('DOOM!', function(err, dialog) {
          if(err) throw err;
          ...
        });
      });
    });
  });
});
A JS-er’s Lament

// Synchronous version of previous slide
try {
  var file = mainWindow.menu("File");
  var menu = file.openMenu();
  var item = menu.item("Open");
  item.click()
  window.createDialog('DOOM!');
} catch (err) {
  ...
}
A Silver Lining

myFunction1();
// No state changes here!
myFunction2();




// Which means we never have to do this...
while (!document.ready) {
  Thread.sleep(0);
}
Mo’ Threads...
Nested Spaghetti
mainWindow.menu("File", function(err, file) {
  if(err) throw err;
  file.openMenu(function(err, menu) {
    if(err) throw err;
    menu.item("Open", function(err, item) {
      if(err) throw err;
      item.click(function(err) {
        if(err) throw err;
        window.createDialog('DOOM!', function(err, dialog) {
          if(err) throw err;
          ...
        });
      });
    });
  });
});
Inflexible APIs
function launchRocketAt(target, callback) {
  var rocket = {x: 0, y: 0},
      step = 0;

    function moveRocket() {
      rocket.x += target.x * (step / 10);
      rocket.y += target.y * (step / 10);
      drawSprite(rocket);
      if (step === 10) {
        callback();
      } else {
        step += 1;
        setTimeout(moveRocket, 50);
      }
    }

    moveRocket();
}
Inflexible APIs


launchRocketAt(target, function() {
  // OK, so the rocket reached its target...
});
Pattern I: PubSub
What is PubSub?

button.on("click", function(event) {
  ...
});


server.on("request", function(req, res, next) {
  ...
});


model.on("change", function() {
  ...
});
What is PubSub for?


• Just about everything!
• When in doubt, use PubSub
How to use it?

• Pick a PubSub library, such as
  https://github.com/Wolfy87/EventEmitter
• If you’re on Node, you already have one
• Simply make your objects inherit from
  EventEmitter, and trigger events on them
An Evented Rocket
Rocket.prototype.launchAt = function(target) {
  rocket = this;
  _.extend(rocket, {x: 0, y: 0, step: 0});

    function moveRocket() {
      // Physics calculations go here...
      if (rocket.step === 10) {
        rocket.emit('complete', rocket);
      } else {
        rock.step += 1;
        setTimeout(moveRocket, 50);
      }
      rocket.emit('moved', rocket);
    }

    rocket.emit('launched', rocket);
    moveRocket();
    return this;
}
An Evented Rocket


var rocket = new Rocket();
rocket.launchAt(target).on('complete', function() {
  // Now it’s obvious what this callback is!
});
PubSub Drawbacks

• No standard
 • Consider using LucidJS:
   https://github.com/RobertWHurst/
   LucidJS
Pattern II: Promises
What is a Promise?

• “A promise represents the eventual value
  returned from the single completion of an
  operation.”
  —The Promises/A Spec
What is a Promise?
• An object that emits an event when an
  async task completes (or fails)


                      Resolved



           Pending



                      Rejected
Example 1: Ajax

var fetchingData = $.get('myData');
fetchingData.done(onSuccess);
fetchingData.fail(onFailure);
fetchingData.state(); // 'pending'

// Additional listeners can be added at any time
fetchingData.done(celebrate);

// `then` is syntactic sugar for done + fail
fetchingData.then(huzzah, alas);
Example 2: Effects

$('#header').fadeTo('fast', 0.5).slideUp('fast');
$('#content').fadeIn('slow');
var animating = $('#header, #content').promise();

animating.done(function() {
  // All of the animations started when promise()
  // was called are now complete.
});
What is a Promise?
• “A promise is a container for an as-yet-
  unknown value, and then’s job is to
  extract the value out of the promise”

  http://blog.jcoglan.com/2013/03/30/
  callbacks-are-imperative-promises-are-
  functional-nodes-biggest-missed-
  opportunity/
Making Promises

// A Promise is a read-only copy of a Deferred
var deferred = $.Deferred();
asyncRead(function(err, data) {
  if (err) {
     deferred.reject();
  } else {
     deferred.resolve(data);
  };
});
var Promise = deferred.promise();
Without Promises
$.fn.loadAndShowContent(function(options) {
  var $el = this;
  function successHandler(content) {
    $el.html(content);
    options.success(content);
  }
  function errorHandler(err) {
    $el.html('Error');
    options.failure(err);
  }
  $.ajax(options.url, {
    success: successHandler,
    error: errorHandler
  });
});
With Promises
$.fn.loadAndShowContent(function(options) {
  var $el = this,
      fetchingContent = $.ajax(options.url);

  fetchingContent.done(function(content) {
    $el.html(content);
  });

  fetchingContent.fail(function(content) {
    $el.html('Error');
  });

  return fetchingContent;
});
Merging Promises

var fetchingData = $.get('myData');
var fadingButton = $button.fadeOut().promise();

$.when(fetchingData, fadingButton)
 .then(function() {
  // Both Promises have resolved
});
Piping Promises

var fetchingPassword = $.get('/password');
fetchingPassword.done(function(password) {
  var loggingIn = $.post('/login', password);
});

// I wish I could attach listeners to the loggingIn
// Promise here... but it doesn’t exist yet!
Piping Promises
var fetchingPassword = $.get('/password');
var loggingIn = fetchingPassword.pipe(function(password) {
  return $.post('/login', password);
});

loggingIn.then(function() {
  // We’ve logged in successfully
}, function(err) {
  // Either the login failed, or the password fetch failed
});

// NOTE: As of jQuery 1.8, then and pipe are synonymous.
// Use `then` for piping if possible.
Piping Promises
var menuFilePromise = mainWindow.menu('file');
var openFilePromise = menuFilePromise.pipe(function(file) {
  return file.openMenu();
});
var menuOpenPromise = openFilePromise.pipe(function(menu) {
  return menu.item('open');
});
var itemClickPromise = menuOpenPromise.pipe(function(item) {
  return item.click()
});
var createDialogPromise = itemClickPromise.pipe(function() {
  return window.createDialog("Promises rock!");
});
A Promise-y Rocket
function launchRocketAt(target) {
  var rocketDeferred = $.Deferred();
  _.extend(rocketDeferred, {x: 0, y: 0, step: 0});

    function moveRocket() {
      // Physics calculations go here...
      rocketDeferred.notify(step / 10);
      if (rocketDeferred.step === 10) {
        rocketDeferred.resolve();
      } else {
        rocketDeferred.step += 1;
        setTimeout(moveRocket, 50);
      }
    }

    moveRocket();
    return rocketDeferred;
}
Promise Drawbacks

• No standard
 • jQuery, Promises/A, Promises/B...
• For maximum benefit, you’ll need
  wrappers all over the place
Pattern III: AMD
What is AMD?

• Asynchronous Module Definition, a spec
• Each module says which modules it needs
• The module’s “factory” is called after all of
  those modules are loaded
What is AMD for?

• Loading dependencies as needed
• Dependency injection (for tests)
• Gating features
How to use AMD

define('myModule', ['jQuery', 'Backbone'],
function($, Backbone) {
  var myModule = {
    // Define some things...
  };

  // If anyone requires this module, they get this object
  return myModule;
});
AMD Drawbacks

• No standard
• Lots of up-front work
• No semantic versioning
• Heavyweight tools (RequireJS)
Alternatives to AMD

• Browserify
 • Simple syntax:   require('./filename');

 • Great if you’re into Node + npm
 • Intended for bundling, not so much for
   async module loading
Conclusion


• The next time you’re about to define a
  function with a callback argument... don’t.
Thanks. Questions?
     @trevorburnham

Sane Async Patterns

  • 1.
    Sane Async Patterns Trevor Burnham HTML5DevConf 2013
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
    In This Talk •Callback arguments considered harmful • Three alternative patterns: • PubSub • Promises • AMD
  • 7.
  • 8.
    Pyramid of Doom mainWindow.menu("File",function(err, file) {   if(err) throw err;   file.openMenu(function(err, menu) {     if(err) throw err;     menu.item("Open", function(err, item) {       if(err) throw err;       item.click(function(err) {         if(err) throw err;         window.createDialog('DOOM!', function(err, dialog) {           if(err) throw err;           ...         });       });     });   }); });
  • 9.
    A JS-er’s Lament //Synchronous version of previous slide try { var file = mainWindow.menu("File");   var menu = file.openMenu();   var item = menu.item("Open");   item.click()   window.createDialog('DOOM!'); } catch (err) { ... }
  • 10.
    A Silver Lining myFunction1(); //No state changes here! myFunction2(); // Which means we never have to do this... while (!document.ready) { Thread.sleep(0); }
  • 11.
  • 12.
    Nested Spaghetti mainWindow.menu("File", function(err,file) {   if(err) throw err;   file.openMenu(function(err, menu) {     if(err) throw err;     menu.item("Open", function(err, item) {       if(err) throw err;       item.click(function(err) {         if(err) throw err;         window.createDialog('DOOM!', function(err, dialog) {           if(err) throw err;           ...         });       });     });   }); });
  • 13.
    Inflexible APIs function launchRocketAt(target,callback) { var rocket = {x: 0, y: 0}, step = 0; function moveRocket() { rocket.x += target.x * (step / 10); rocket.y += target.y * (step / 10); drawSprite(rocket); if (step === 10) { callback(); } else { step += 1; setTimeout(moveRocket, 50); } } moveRocket(); }
  • 14.
    Inflexible APIs launchRocketAt(target, function(){ // OK, so the rocket reached its target... });
  • 15.
  • 16.
    What is PubSub? button.on("click",function(event) { ... }); server.on("request", function(req, res, next) { ... }); model.on("change", function() { ... });
  • 17.
    What is PubSubfor? • Just about everything! • When in doubt, use PubSub
  • 18.
    How to useit? • Pick a PubSub library, such as https://github.com/Wolfy87/EventEmitter • If you’re on Node, you already have one • Simply make your objects inherit from EventEmitter, and trigger events on them
  • 19.
    An Evented Rocket Rocket.prototype.launchAt= function(target) { rocket = this; _.extend(rocket, {x: 0, y: 0, step: 0}); function moveRocket() { // Physics calculations go here... if (rocket.step === 10) { rocket.emit('complete', rocket); } else { rock.step += 1; setTimeout(moveRocket, 50); } rocket.emit('moved', rocket); } rocket.emit('launched', rocket); moveRocket(); return this; }
  • 20.
    An Evented Rocket varrocket = new Rocket(); rocket.launchAt(target).on('complete', function() { // Now it’s obvious what this callback is! });
  • 21.
    PubSub Drawbacks • Nostandard • Consider using LucidJS: https://github.com/RobertWHurst/ LucidJS
  • 22.
  • 23.
    What is aPromise? • “A promise represents the eventual value returned from the single completion of an operation.” —The Promises/A Spec
  • 24.
    What is aPromise? • An object that emits an event when an async task completes (or fails) Resolved Pending Rejected
  • 25.
    Example 1: Ajax varfetchingData = $.get('myData'); fetchingData.done(onSuccess); fetchingData.fail(onFailure); fetchingData.state(); // 'pending' // Additional listeners can be added at any time fetchingData.done(celebrate); // `then` is syntactic sugar for done + fail fetchingData.then(huzzah, alas);
  • 26.
    Example 2: Effects $('#header').fadeTo('fast',0.5).slideUp('fast'); $('#content').fadeIn('slow'); var animating = $('#header, #content').promise(); animating.done(function() { // All of the animations started when promise() // was called are now complete. });
  • 27.
    What is aPromise? • “A promise is a container for an as-yet- unknown value, and then’s job is to extract the value out of the promise” http://blog.jcoglan.com/2013/03/30/ callbacks-are-imperative-promises-are- functional-nodes-biggest-missed- opportunity/
  • 28.
    Making Promises // APromise is a read-only copy of a Deferred var deferred = $.Deferred(); asyncRead(function(err, data) { if (err) { deferred.reject(); } else { deferred.resolve(data); }; }); var Promise = deferred.promise();
  • 29.
    Without Promises $.fn.loadAndShowContent(function(options) { var $el = this; function successHandler(content) { $el.html(content); options.success(content); } function errorHandler(err) { $el.html('Error'); options.failure(err); } $.ajax(options.url, { success: successHandler, error: errorHandler }); });
  • 30.
    With Promises $.fn.loadAndShowContent(function(options) { var $el = this, fetchingContent = $.ajax(options.url); fetchingContent.done(function(content) { $el.html(content); }); fetchingContent.fail(function(content) { $el.html('Error'); }); return fetchingContent; });
  • 31.
    Merging Promises var fetchingData= $.get('myData'); var fadingButton = $button.fadeOut().promise(); $.when(fetchingData, fadingButton) .then(function() { // Both Promises have resolved });
  • 32.
    Piping Promises var fetchingPassword= $.get('/password'); fetchingPassword.done(function(password) { var loggingIn = $.post('/login', password); }); // I wish I could attach listeners to the loggingIn // Promise here... but it doesn’t exist yet!
  • 33.
    Piping Promises var fetchingPassword= $.get('/password'); var loggingIn = fetchingPassword.pipe(function(password) { return $.post('/login', password); }); loggingIn.then(function() { // We’ve logged in successfully }, function(err) { // Either the login failed, or the password fetch failed }); // NOTE: As of jQuery 1.8, then and pipe are synonymous. // Use `then` for piping if possible.
  • 34.
    Piping Promises var menuFilePromise= mainWindow.menu('file'); var openFilePromise = menuFilePromise.pipe(function(file) {   return file.openMenu(); }); var menuOpenPromise = openFilePromise.pipe(function(menu) {   return menu.item('open'); }); var itemClickPromise = menuOpenPromise.pipe(function(item) {   return item.click() }); var createDialogPromise = itemClickPromise.pipe(function() {   return window.createDialog("Promises rock!"); });
  • 35.
    A Promise-y Rocket functionlaunchRocketAt(target) { var rocketDeferred = $.Deferred(); _.extend(rocketDeferred, {x: 0, y: 0, step: 0}); function moveRocket() { // Physics calculations go here... rocketDeferred.notify(step / 10); if (rocketDeferred.step === 10) { rocketDeferred.resolve(); } else { rocketDeferred.step += 1; setTimeout(moveRocket, 50); } } moveRocket(); return rocketDeferred; }
  • 36.
    Promise Drawbacks • Nostandard • jQuery, Promises/A, Promises/B... • For maximum benefit, you’ll need wrappers all over the place
  • 37.
  • 38.
    What is AMD? •Asynchronous Module Definition, a spec • Each module says which modules it needs • The module’s “factory” is called after all of those modules are loaded
  • 39.
    What is AMDfor? • Loading dependencies as needed • Dependency injection (for tests) • Gating features
  • 40.
    How to useAMD define('myModule', ['jQuery', 'Backbone'], function($, Backbone) { var myModule = { // Define some things... }; // If anyone requires this module, they get this object return myModule; });
  • 41.
    AMD Drawbacks • Nostandard • Lots of up-front work • No semantic versioning • Heavyweight tools (RequireJS)
  • 42.
    Alternatives to AMD •Browserify • Simple syntax: require('./filename'); • Great if you’re into Node + npm • Intended for bundling, not so much for async module loading
  • 43.
    Conclusion • The nexttime you’re about to define a function with a callback argument... don’t.
  • 44.
    Thanks. Questions? @trevorburnham