How to *actually* use promises in JavaScript

How to *actually* use promises in JavaScript

7dd731d0c97e334d726f740a710904a9?s=128

Jakob Mattsson

February 25, 2014
Tweet

Transcript

  1. jakobm.com @jakobmattsson I’m a coder first and foremost. I also

    help companies recruit coders, train coders and architect software. Sometimes I do technical due diligence and speak at conferences. ! Want more? Read my story or blog.
  2. From the Swedish depths

  3. Social coding

  4. FishBrain is the fastest, easiest way to log and share

    your fishing. ! Get instant updates from anglers on nearby lakes, rivers, and the coast.
  5. Enough with the madness

  6. Enough with the madness The lack and/or misuse of promises

  7. Promises 101* *No, this is not a tutorial

  8. getJSON({ url: '/somewhere/over/the/rainbow', success: function(result) { // deal with it

    } });
  9. getJSON({ url: '/somewhere/over/the/rainbow', success: function(result) { // deal with it

    } }); rainbows.then(function(result) { // deal with it }); var rainbows = getJSON({ url: '/somewhere/over/the/rainbow' });
  10. getJSON({ url: '/somewhere/over/the/rainbow', success: function(result) { // deal with it

    } }); rainbows.then(function(result) { // deal with it }); var rainbows = getJSON({ url: '/somewhere/over/the/rainbow' }); f(rainbows); // such preprocessing!
  11. Why is that a good idea? • Loosen up the

    coupling • Superior error handling • Simplified async
  12. That’s enough 101

  13. Promises are not new

  14. Promises are not new http://github.com/kriskowal/q http://www.html5rocks.com/en/tutorials/es6/promises http://domenic.me/2012/10/14/youre-missing-the-point-of-promises http://www.promisejs.org https://github.com/bellbind/using-promise-q https://github.com/tildeio/rsvp.js

    https://github.com/cujojs/when
  15. They’ve even made it into ES6

  16. They’ve even made it into ES6 Already implemented natively in

    Firefox 30 Chrome 33
  17. So why are you not using them?

  18. So why are you not using them?

  19. How to draw an owl

  20. 1. Draw some circles How to draw an owl

  21. 1. Draw some circles 2. Draw the rest of the

    fucking owl How to draw an owl
  22. We want to draw owls. ! Not circles.

  23. getJSON('story.json').then(function(story) { addHtmlToPage(story.heading); ! // Map our array of chapter

    urls to // an array of chapter json promises. // This makes sture they all download parallel. return story.chapterUrls.map(getJSON) .reduce(function(sequence, chapterPromise) { // Use reduce to chain the promises together, // adding content to the page for each chapter return sequence.then(function() { // Wait for everything in the sequence so far, // then wait for this chapter to arrive. return chapterPromise; }).then(function(chapter) { addHtmlToPage(chapter.html); }); }, Promise.resolve()); }).then(function() { addTextToPage('All done'); }).catch(function(err) { // catch any error that happened along the way addTextToPage("Argh, broken: " + err.message); }).then(function() { document.querySelector('.spinner').style.display = 'none'; }); As announced for ES6
  24. That’s circles. ! Nice circles! ! Still circles.

  25. browser.init({browserName:'chrome'}, function() { browser.get("http://admc.io/wd/test-pages/guinea-pig.html", function() { browser.title(function(err, title) { title.should.include('WD');

    browser.elementById('i am a link', function(err, el) { browser.clickElement(el, function() { browser.eval("window.location.href", function(err, href) { href.should.include('guinea-pig2'); browser.quit(); }); }); }); }); }); }); Node.js WebDriver Before promises
  26. browser .init({ browserName: 'chrome' }) .then(function() { return browser.get("http://admc.io/wd/test-pages/guinea-pig.html"); })

    .then(function() { return browser.title(); }) .then(function(title) { title.should.include('WD'); return browser.elementById('i am a link'); }) .then(function(el) { return browser.clickElement(el); }) .then(function() { return browser.eval("window.location.href"); }) .then(function(href) { href.should.include('guinea-pig2'); }) .fin(function() { return browser.quit(); }) .done(); After promises
  27. Used to be callback-hell. ! Now it is then-hell.

  28. None
  29. None
  30. These examples are just a different way of doing async.

    ! It’s still uncomfortable. It’s still circles!
  31. The point of promises:

  32. ! Make async code as easy to write as sync

    code The point of promises:
  33. 1 Promises out: Always return promises - not callback 2

    Promises in: Functions should accept promises as well as regular values 3 Promises between: Augment promises as you augment regular objects Three requirements
  34. Let me elaborate

  35. Example - Testing a blog API • Create a blog

    • Create two blog entries • Create some users • Create some comments from those users, on the two posts • Request the stats for the blog and check if the given number of entries and comments are correct
  36. post('/blogs', { name: 'My blog' }, function(err, blog) { !

    post(concatUrl('blogs', blog.id, 'entries'), { title: 'my first post’, body: 'Here is the text of my first post' }, function(err, entry1) { ! post(concatUrl('blogs', blog.id, 'entries'), { title: 'my second post’, body: 'I do not know what to write any more...' }, function(err, entry2) { ! post('/user', { name: 'john doe’ }, function(err, visitor1) { ! post('/user', { name: 'jane doe' }, function(err, visitor2) { ! post('/comments', { userId: visitor1.id, entryId: entry1.id, text: "well written dude" }, function(err, comment1) { ! post('/comments', { userId: visitor2.id, entryId: entry1.id, text: "like it!" }, function(err, comment2) { ! post('/comments', { userId: visitor2.id, entryId: entry2.id, text: "nah, crap" }, function(err, comment3) { ! get(concatUrl('blogs', blog.id), function(err, blogInfo) { assertEquals(blogInfo, { name: 'My blog', numberOfEntries: 2, numberOfComments: 3 }); }); }); }); }); }); }); }); }); }); https:// github.com/ jakobmattsson/ z-presentation/ blob/master/ promises-in-out/ 1-naive.js 1 Note: without narration, this slide lacks a lot of context. Open the file above and read the commented version for the full story.
  37. post('/blogs', { name: 'My blog' }, function(err, blog) { !

    var entryData = [{ title: 'my first post', body: 'Here is the text of my first post' }, { title: 'my second post', body: 'I do not know what to write any more...' }] ! async.forEach(entryData, function(entry, callback), { post(concatUrl('blogs', blog.id, 'entries'), entry, callback); }, function(err, entries) { ! var usernames = ['john doe', 'jane doe']; ! async.forEach(usernames, function(user, callback) { post('/user', { name: user }, callback); }, function(err, visitors) { ! var commentsData = [{ userId: visitor[0].id, entryId: entries[0].id, text: "well written dude" }, { userId: visitor[1].id, entryId: entries[0].id, text: "like it!" }, { userId: visitor[1].id, entryId: entries[1].id, text: "nah, crap" }]; ! async.forEach(commentsData, function(comment, callback) { post('/comments', comment, callback); }, function(err, comments) { ! get(concatUrl('blogs', blog.id), function(err, blogInfo) { ! assertEquals(blogInfo, { name: 'My blog', numberOfEntries: 2, numberOfComments: 3 }); }); }); }); }); }); https:// github.com/ jakobmattsson/ z-presentation/ blob/master/ promises-in-out/ 2-async.js 2 Note: without narration, this slide lacks a lot of context. Open the file above and read the commented version for the full story.
  38. https:// github.com/ jakobmattsson/ z-presentation/ blob/master/ promises-in-out/ 3-async-more-parallel.js 3 Note: without

    narration, this slide lacks a lot of context. Open the file above and read the commented version for the full story. post('/blogs', { name: 'My blog' }, function(err, blog) { ! async.parallel([ function(callback) { var entryData = [{ title: 'my first post', body: 'Here is the text of my first post' }, { title: 'my second post', body: 'I do not know what to write any more...' }]; async.forEach(entryData, function(entry, callback), { post(concatUrl('blogs', blog.id, 'entries'), entry, callback); }, callback); }, function(callback) { var usernames = ['john doe', 'jane doe’]; async.forEach(usernames, function(user, callback) { post('/user', { name: user }, callback); }, callback); } ], function(err, results) { ! var entries = results[0]; var visitors = results[1]; ! var commentsData = [{ userId: visitors[0].id, entryId: entries[0].id, text: "well written dude" }, { userId: visitors[1].id, entryId: entries[0].id, text: "like it!" }, { userId: visitors[1].id, entryId: entries[1].id, text: "nah, crap" }]; ! async.forEach(commentsData, function(comment, callback) { post('/comments', comment, callback); }, function(err, comments) { ! get(concatUrl('blogs', blog.id), function(err, blogInfo) { assertEquals(blogInfo, { name: 'My blog', numberOfEntries: 2, numberOfComments: 3 }); }); }); }); });
  39. post('/blogs', { name: 'My blog' }).then(function(blog) { ! var visitor1

    = post('/user', { name: 'john doe' }); ! var visitor2 = post('/user', { name: 'jane doe' }); ! var entry1 = post(concatUrl('blogs', blog.id, 'entries'), { title: 'my first post', body: 'Here is the text of my first post' }); ! var entry2 = post(concatUrl('blogs', blog.id, 'entries'), { title: 'my second post', body: 'I do not know what to write any more...' }); ! var comment1 = all(entry1, visitor1).then(function(e1, v1) { post('/comments', { userId: v1.id, entryId: e1.id, text: "well written dude" }); }); ! var comment2 = all(entry1, visitor2).then(function(e1, v2) { post('/comments', { userId: v2.id, entryId: e1.id, text: "like it!" }); }); ! var comment3 = all(entry2, visitor2).then(function(e2, v2) { post('/comments', { userId: v2.id, entryId: e2.id, text: "nah, crap" }); }); ! all(comment1, comment2, comment3).then(function() { get(concatUrl('blogs', blog.id)).then(function(blogInfo) { assertEquals(blogInfo, { name: 'My blog', numberOfEntries: 2, numberOfComments: 3 }); }); }); }); https:// github.com/ jakobmattsson/ z-presentation/ blob/master/ promises-in-out/ 4-promises-convoluted.js 4 Note: without narration, this slide lacks a lot of context. Open the file above and read the commented version for the full story.
  40. var blog = post('/blogs', { name: 'My blog' }); !

    var entry1 = post(concatUrl('blogs', blog.get('id'), 'entries'), { title: 'my first post', body: 'Here is the text of my first post' }); ! var entry2 = post(concatUrl('blogs', blog.get('id'), 'entries'), { title: 'my second post', body: 'I do not know what to write any more...' }); ! var visitor1 = post('/user', { name: 'john doe' }); ! var visitor2 = post('/user', { name: 'jane doe' }); ! var comment1 = post('/comments', { userId: visitor1.get('id'), entryId: entry1.get('id'), text: "well written dude" }); ! var comment2 = post('/comments', { userId: visitor2.get('id'), entryId: entry1.get('id'), text: "like it!" }); ! var comment3 = post('/comments', { userId: visitor2.get('id'), entryId: entry2.get('id'), text: "nah, crap" }); ! var allComments = [comment1, comment2, comment2]; ! var blogInfoUrl = concatUrl('blogs', blog.get('id')); ! var blogInfo = getAfter(blogInfoUrl, allComments); ! assertEquals(blogInfo, { name: 'My blog', numberOfEntries: 2, numberOfComments: 3 }); https:// github.com/ jakobmattsson/ z-presentation/ blob/master/ promises-in-out/ 5-promises-nice.js 5 Note: without narration, this slide lacks a lot of context. Open the file above and read the commented version for the full story.
  41. None
  42. 1 Promises out: Always return promises - not callback 2

    Promises in: Functions should accept promises as well as regular values 3 Promises between: Augment promises as you augment regular objects Three requirements
  43. Augmenting objects is a sensitive topic

  44. Extending prototypes ! vs ! wrapping objects

  45. You’re writing Course of action An app Do whatever you

    want A library Do not fucking modify the
 god damn prototypes Complimentary decision matrix
  46. None
  47. Promises are already wrapped objects

  48. _(names) .chain() .unique() .shuffle() .first(3) .value() Chaining usually requires a

    method to ”unwrap” or repeated wrapping u = _(names).unique() s = _(u).shuffle() f = _(s).first(3)
  49. Promises already have a well-defined way of unwrapping.

  50. _(names) .unique() .shuffle() .first(3) .then(function(values) { // do stuff with

    values... }) If underscore/lodash wrapped promises
  51. But they don’t

  52. Enter ! Z

  53. 1 Deep resolution: Resolve any kind of object/array/promise/values/whatever 2 Make

    functions promise-friendly: Sync or async doesn’t matter; will accept promises 3 Augmentation for promises: jQuery/ underscore/lodash-like extensions What is Z?
  54. Deep resolution var data = { userId: visitor1.get('id'), entryId: entry1.get('id'),

    text: 'well written dude' }; ! Z(data).then(function(result) { ! // result is now: { // userId: 123, // entryId: 456, // text: 'well written dude' // } ! }); Takes any object and resolves all promises in it. ! Like Q and Q.all, but deep 1
  55. Promise-friendly functions var post = function(url, data, callback) { //

    POSTs `data` to `url` and // then invokes `callback` }; ! post = Z.bindAsync(post); ! var comment1 = post('/comments', { userId: visitor1.get('id'), entryId: entry1.get('id'), text: 'well written dude' }); bindAsync creates a function that takes promises as arguments and returns a promise. 2
  56. Promise-friendly functions var add = function(x, y) { return x

    + y; }; ! add = Z.bindSync(add); ! var sum = add(v1.get('id'), e1.get('id')); ! var comment1 = post('/comments', { userId: visitor1.get('id'), entryId: entry1.get('id'), text: 'well written dude', likes: sum }); 2 bindSync does that same, for functions that are not async
  57. Augmentation for promises var commentData = get('/comments/42'); ! var text

    = commentData.get('text'); ! var lowerCased = text.then(function(text) { return text.toLowerCase(); }); 3 Without augmentation every operation has to be wrapped in ”then”
  58. None
  59. Augmentation for promises Z.mixin({ toLowerCase: function() { return this.value.toLowerCase(); }

    }); ! var commentData = get('/comments/42'); ! commentData.get('text').toLowerCase(); 3 Z has mixin to solve this ! Note that Z is not explicitly applied to the promise
  60. Augmentation for promises Z.mixin(zUnderscore); Z.mixin(zBuiltins); ! var comment = get('/comments/42');

    ! comment.get('text').toLowerCase().first(5); 3 There are prepared packages to mixin entire libraries
  61. 1 Promises out: Always return promises - not callback 2

    Promises in: Functions should accept promises as well as regular values 3 Promises between: Augment promises as you augment regular objects Three requirements
  62. Make async code as easy to write as sync code

    Enough with the madness
  63. You don’t have to use Z to do these things

  64. But FFS

  65. None
  66. #noandthen www.jakobm.com @jakobmattsson ! github.com/jakobmattsson/z-core bit.ly/sthlmjs + - Enough with

    the madness