Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How to *actually* use promises in JavaScript

How to *actually* use promises in JavaScript

Jakob Mattsson

February 25, 2014
Tweet

More Decks by Jakob Mattsson

Other Decks in Programming

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. FishBrain is the fastest, easiest way to log and share

    your fishing. ! Get instant updates from anglers on nearby lakes, rivers, and the coast.
  3. 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' });
  4. 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!
  5. Why is that a good idea? • Loosen up the

    coupling • Superior error handling • Simplified async
  6. 1. Draw some circles 2. Draw the rest of the

    fucking owl How to draw an owl
  7. 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
  8. 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
  9. 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
  10. These examples are just a different way of doing async.

    ! It’s still uncomfortable. It’s still circles!
  11. ! Make async code as easy to write as sync

    code The point of promises:
  12. 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
  13. 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
  14. 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.
  15. 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.
  16. 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 }); }); }); }); });
  17. 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.
  18. 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.
  19. 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
  20. 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
  21. _(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)
  22. 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?
  23. 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
  24. 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
  25. 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
  26. 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”
  27. 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
  28. 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
  29. 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