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.

    View Slide

  2. From the
    Swedish
    depths

    View Slide

  3. Social
    coding

    View Slide

  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.

    View Slide

  5. Enough with
    the madness

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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!

    View Slide

  11. Why is that
    a good idea?
    • Loosen up the coupling
    • Superior error handling
    • Simplified async

    View Slide

  12. That’s enough 101

    View Slide

  13. Promises are not new

    View Slide

  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

    View Slide

  15. They’ve even
    made it into ES6

    View Slide

  16. They’ve even
    made it into ES6
    Already implemented
    natively in
    Firefox 30
    Chrome 33

    View Slide

  17. So why are you not
    using them?

    View Slide

  18. So why are you not
    using them?

    View Slide

  19. How to draw an owl

    View Slide

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

    View Slide

  21. 1. Draw some
    circles
    2. Draw the rest of
    the fucking owl
    How to draw an owl

    View Slide

  22. We want to
    draw owls.
    !
    Not circles.

    View Slide

  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

    View Slide

  24. That’s circles.
    !
    Nice circles!
    !
    Still circles.

    View Slide

  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

    View Slide

  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

    View Slide

  27. Used to be
    callback-hell.
    !
    Now it is
    then-hell.

    View Slide

  28. View Slide

  29. View Slide

  30. These examples are
    just a different way
    of doing async.
    !
    It’s still uncomfortable.
    It’s still circles!

    View Slide

  31. The point of promises:

    View Slide

  32. !
    Make async code
    as easy to write
    as sync code
    The point of promises:

    View Slide

  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

    View Slide

  34. Let me elaborate

    View Slide

  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

    View Slide

  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.

    View Slide

  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.

    View Slide

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

    View Slide

  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.

    View Slide

  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.

    View Slide

  41. View Slide

  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

    View Slide

  43. Augmenting objects
    is a sensitive topic

    View Slide

  44. Extending prototypes
    !
    vs
    !
    wrapping objects

    View Slide

  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

    View Slide

  46. View Slide

  47. Promises are already
    wrapped objects

    View Slide

  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)

    View Slide

  49. Promises already have a
    well-defined way of
    unwrapping.

    View Slide

  50. _(names)
    .unique()
    .shuffle()
    .first(3)
    .then(function(values) {
    // do stuff with values...
    })
    If underscore/lodash
    wrapped promises

    View Slide

  51. But they don’t

    View Slide

  52. Enter
    !
    Z

    View Slide

  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?

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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”

    View Slide

  58. View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  62. Make async code
    as easy to write
    as sync code
    Enough with
    the madness

    View Slide

  63. You don’t have to use
    Z to do these things

    View Slide

  64. But FFS

    View Slide

  65. View Slide

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

    View Slide