Successfully reported this slideshow.
Your SlideShare is downloading. ×

Sane Async Patterns

Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Loading in …3
×

Check these out next

1 of 44 Ad

More Related Content

Slideshows for you (20)

Advertisement

Similar to Sane Async Patterns (20)

Recently uploaded (20)

Advertisement

Sane Async Patterns

  1. 1. Sane Async Patterns Trevor Burnham HTML5DevConf 2013
  2. 2. http://dev.hubspot.com/jobs
  3. 3. https://pragprog.com/books/tbcoffee
  4. 4. https://pragprog.com/books/tbajs
  5. 5. https://leanpub.com/npm
  6. 6. In This Talk • Callback arguments considered harmful • Three alternative patterns: • PubSub • Promises • AMD
  7. 7. The Callback Argument Anti-Pattern
  8. 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. 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. 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. 11. Mo’ Threads...
  12. 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. 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. 14. Inflexible APIs launchRocketAt(target, function() { // OK, so the rocket reached its target... });
  15. 15. Pattern I: PubSub
  16. 16. What is PubSub? button.on("click", function(event) { ... }); server.on("request", function(req, res, next) { ... }); model.on("change", function() { ... });
  17. 17. What is PubSub for? • Just about everything! • When in doubt, use PubSub
  18. 18. 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
  19. 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. 20. An Evented Rocket var rocket = new Rocket(); rocket.launchAt(target).on('complete', function() { // Now it’s obvious what this callback is! });
  21. 21. PubSub Drawbacks • No standard • Consider using LucidJS: https://github.com/RobertWHurst/ LucidJS
  22. 22. Pattern II: Promises
  23. 23. What is a Promise? • “A promise represents the eventual value returned from the single completion of an operation.” —The Promises/A Spec
  24. 24. What is a Promise? • An object that emits an event when an async task completes (or fails) Resolved Pending Rejected
  25. 25. 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);
  26. 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. 27. 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/
  28. 28. 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();
  29. 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. 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. 31. Merging Promises var fetchingData = $.get('myData'); var fadingButton = $button.fadeOut().promise(); $.when(fetchingData, fadingButton) .then(function() { // Both Promises have resolved });
  32. 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. 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. 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. 35. 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; }
  36. 36. Promise Drawbacks • No standard • jQuery, Promises/A, Promises/B... • For maximum benefit, you’ll need wrappers all over the place
  37. 37. Pattern III: AMD
  38. 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. 39. What is AMD for? • Loading dependencies as needed • Dependency injection (for tests) • Gating features
  40. 40. 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; });
  41. 41. AMD Drawbacks • No standard • Lots of up-front work • No semantic versioning • Heavyweight tools (RequireJS)
  42. 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. 43. Conclusion • The next time you’re about to define a function with a callback argument... don’t.
  44. 44. Thanks. Questions? @trevorburnham

×