Sane Async Patterns

29,543 views

Published on

Talk given at HTML5DevConf on April 1, 2013.

Published in: Technology
3 Comments
29 Likes
Statistics
Notes
  • _love_ 'What is PubSub?'
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • What I meant is that there is no single, ubiquitous standard. If an unfamiliar project's documentation says 'This function returns a Promise,' you don't know how to talk to it. If your project uses both jQuery Promises and Promises/A+ Promises, there's going to be confusion. Promises/A+ is a fine standard; unfortunately, it's not the only one.
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • On slide 36, you mention there is no standard for Promises. However, Promises/A+, a community developed standard, has a well-written specification and multiple interoperable implementations, including RSVP in Ember.js, Q, and When.js. The spec and list of implementations may be found at https://github.com/promises-aplus/promises-spec
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Views
Total views
29,543
On SlideShare
0
From Embeds
0
Number of Embeds
10,819
Actions
Shares
0
Downloads
94
Comments
3
Likes
29
Embeds 0
No embeds

No notes for slide

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 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;          ...        });      });    });  });});
  9. 9. 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) { ...}
  10. 10. A Silver LiningmyFunction1();// 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 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;          ...        });      });    });  });});
  13. 13. 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();}
  14. 14. Inflexible APIslaunchRocketAt(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 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;}
  20. 20. An Evented Rocketvar 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: Ajaxvar 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);
  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 Deferredvar 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 Promisesvar fetchingData = $.get(myData);var fadingButton = $button.fadeOut().promise();$.when(fetchingData, fadingButton) .then(function() { // Both Promises have resolved});
  32. 32. Piping Promisesvar 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 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.
  34. 34. 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!");});
  35. 35. 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;}
  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 AMDdefine(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

×