44
Sane Async Patterns Trevor Burnham HTML5DevConf 2013

Sane Async Patterns

Embed Size (px)

DESCRIPTION

Talk given at HTML5DevConf on April 1, 2013.

Citation preview

Page 1: Sane Async Patterns

Sane Async PatternsTrevor Burnham

HTML5DevConf 2013

Page 2: Sane Async Patterns

http://dev.hubspot.com/jobs

Page 3: Sane Async Patterns

https://pragprog.com/books/tbcoffee

Page 5: Sane Async Patterns

https://leanpub.com/npm

Page 6: Sane Async Patterns

In This Talk

• Callback arguments considered harmful

• Three alternative patterns:

• PubSub

• Promises

• AMD

Page 7: Sane Async Patterns

The Callback ArgumentAnti-Pattern

Page 8: Sane Async Patterns

Pyramid of DoommainWindow.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;           ...         });       });     });   }); });

Page 9: Sane Async Patterns

A JS-er’s Lament

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

Page 10: Sane Async Patterns

A Silver Lining

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

// Which means we never have to do this...while (!document.ready) { Thread.sleep(0);}

Page 11: Sane Async Patterns

Mo’ Threads...

Page 12: Sane Async Patterns

Nested SpaghettimainWindow.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;           ...         });       });     });   }); });

Page 13: Sane Async Patterns

Inflexible APIsfunction 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();}

Page 14: Sane Async Patterns

Inflexible APIs

launchRocketAt(target, function() { // OK, so the rocket reached its target...});

Page 15: Sane Async Patterns

Pattern I: PubSub

Page 16: Sane Async Patterns

What is PubSub?

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

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

model.on("change", function() { ...});

Page 17: Sane Async Patterns

What is PubSub for?

• Just about everything!

• When in doubt, use PubSub

Page 18: Sane Async Patterns

How to use it?

• Pick a PubSub library, such ashttps://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

Page 19: Sane Async Patterns

An Evented RocketRocket.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;}

Page 20: Sane Async Patterns

An Evented Rocket

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

Page 21: Sane Async Patterns

PubSub Drawbacks

• No standard

• Consider using LucidJS:https://github.com/RobertWHurst/LucidJS

Page 22: Sane Async Patterns

Pattern II: Promises

Page 23: Sane Async Patterns

What is a Promise?

• “A promise represents the eventual value returned from the single completion of an operation.”—The Promises/A Spec

Page 24: Sane Async Patterns

What is a Promise?

• An object that emits an event when an async task completes (or fails)

Pending

Resolved

Rejected

Page 25: Sane Async Patterns

Example 1: Ajax

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

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

// `then` is syntactic sugar for done + failfetchingData.then(huzzah, alas);

Page 26: Sane Async Patterns

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.});

Page 28: Sane Async Patterns

Making Promises

// A Promise is a read-only copy of a Deferredvar deferred = $.Deferred();asyncRead(function(err, data) { if (err) { deferred.reject(); } else { deferred.resolve(data); };});var Promise = deferred.promise();

Page 29: Sane Async Patterns

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 });});

Page 30: Sane Async Patterns

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;});

Page 31: Sane Async Patterns

Merging Promises

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

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

Page 32: Sane Async Patterns

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!

Page 33: Sane Async Patterns

Piping Promisesvar 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.

Page 34: Sane Async Patterns

Piping Promisesvar 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!");});

Page 35: Sane Async Patterns

A Promise-y Rocketfunction 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;}

Page 36: Sane Async Patterns

Promise Drawbacks

• No standard

• jQuery, Promises/A, Promises/B...

• For maximum benefit, you’ll need wrappers all over the place

Page 37: Sane Async Patterns

Pattern III: AMD

Page 38: Sane Async Patterns

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

Page 39: Sane Async Patterns

What is AMD for?

• Loading dependencies as needed

• Dependency injection (for tests)

• Gating features

Page 40: Sane Async Patterns

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;});

Page 41: Sane Async Patterns

AMD Drawbacks

• No standard

• Lots of up-front work

• No semantic versioning

• Heavyweight tools (RequireJS)

Page 42: Sane Async Patterns

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

Page 43: Sane Async Patterns

Conclusion

• The next time you’re about to define a function with a callback argument... don’t.

Page 44: Sane Async Patterns

Thanks. Questions?@trevorburnham