$30 off During Our Annual Pro Sale. View Details »

Promises @ LondonJS

Mark Wubben
November 19, 2012

Promises @ LondonJS

An introductory talk to Promises, given at LondonJS on November 19th, 2012.

Mark Wubben

November 19, 2012
Tweet

More Decks by Mark Wubben

Other Decks in Programming

Transcript

  1. Mark Wubben
    @novemberborn
    LondonJS,
    November 19th
    2012
    Promises
    A talk about Promises, given at LondonJS on November 19th, 2012.
    Photo by Sergiu Bacioiu, CC-BY-NC-2.0, http://www.flickr.com/photos/sergiu_bacioiu/5145619202/

    View Slide

  2. var value = getValue();
    Imagine a function that returns a value. In it's simplest form we assign that value to a
    variable. The operation is synchronous. But this is JavaScript, and we're not often
    synchronous!

    View Slide

  3. getValue(function(value){
    });
    So we pass a callback to the function, to be invoked once it has our value. Simple enough,
    until you've gotten yourself trapped in callback hell.

    View Slide

  4. var p = getValue();
    What if our function could return an object that promises a value will be provided in the
    future?

    View Slide

  5. var p = getValue();
    p.then(function(value){
    });
    And what if we could add handlers to that object so we can be called whenever the value is
    available?

    View Slide

  6. // Much, much later…
    p.then(function(value){
    });
    What if even if we add this callback much, much later, we can still get the same value?

    View Slide

  7. So that's promises. Objects that promise a value in some future turn of the event loop, to
    which you can add handlers before or after the promise is fulfilled, and which always call the
    handlers with the same value.
    Photo by Alexandre Normand, CC-BY-2.0, http://www.flickr.com/photos/alexnormand/
    5992512756/

    View Slide

  8. Done!

    View Slide

  9. Of course there's more to it than that. Promises are a useful abstraction that comes with its
    own set of coding patterns.
    Photo by Eric (illuminaut), CC-BY-NC-SA-2.0, http://www.flickr.com/photos/illuminaut/
    3954750954/

    View Slide

  10. getValue().then(function(value){
    });
    So, recap. getValue() returns a promise, and we use then() to add a handler for when that
    promise is fulfilled.

    View Slide

  11. function square(n){ return n * n; }
    getValue() // 2
    .then(square); // 4
    Let's say our function fulfills the promise with a value of 2. We then transform that value
    through the square method, resulting in 4.
    But how do we read this value?

    View Slide

  12. function square(n){ return n * n; }
    var result = getValue().then(square);
    result.then(log); // --> 4
    then() returns a promise for the result of the handler. This means you can chain promises.

    View Slide

  13. getValue() // 2
    .then(square) // 4
    .then(square) // 16
    .then(log) // --> 16

    View Slide

  14. getValue().then(function(m){
    return getOtherValue().then(function(n){
    return m * n; // 2 * 4
    });
    }).then(square) // 64
    .then(log); // --> 64
    But what if the handler returns a promise instead of a value? The chain will wait for that
    promise to be resolved.

    View Slide

  15. var p1 = getValue();
    var p2 = p1.then(function(m){
    var q1 = getOtherValue();
    var q2 = q1.then(function(n){
    return m * n;
    });
    return q2; // 2 * 4
    });
    var p3 = p2.then(square); // 64
    p3.then(log); // --> 64
    Here's the same example, but with the promises named.

    View Slide

  16. function getContacts(username){
    return findUser(username)
    .then(lookupContactsForUser);
    }
    getContacts("novemberborn")
    .then(function(contacts){
    });
    This allows you to compose asynchronous operations. For example a two step lookup to find
    contacts for a particular user.

    View Slide

  17. function getContacts(username, cb){
    findUser(username, function(user){
    lookupContactsForUser(user, cb);
    });
    }
    getContacts("username", function(contacts){
    });
    As contrast, here's a callback based solution. I personally much prefer the promise based
    code!

    View Slide

  18. function getContacts(username, cb){
    findUser(username, function(err, user){
    if(err){
    cb(err);
    }else{
    lookupContactsForUser(user, cb);
    }
    });
    }
    getContacts(
    "username",
    function(err, contacts){
    }
    );
    Especially once you add error handling…

    View Slide

  19. function getContacts(username){
    return findUser(username)
    .then(lookupContactsForUser);
    }
    getContacts("novemberborn")
    .then(function(contacts){
    }, function(reason){
    });
    We can add error handling to our promise code, too. Can you tell the difference?

    View Slide

  20. function getContacts(username){
    return findUser(username)
    .then(lookupContactsForUser);
    }
    getContacts("novemberborn")
    .then(function(contacts){
    });
    The original code, for reference.

    View Slide

  21. function getContacts(username){
    return findUser(username)
    .then(lookupContactsForUser);
    }
    getContacts("novemberborn")
    .then(function(contacts){
    }, function(reason){
    });
    Errors are handled by a different method! This means they'll never interfere with your normal
    handler. In effect there is a second communication channel.

    View Slide

  22. Pending -> Fulfilled
    Pending -> Rejected
    A promise can be in either of three states:
    * Pending: waiting to be fulfilled or rejected
    * Fulfilled: successfully fulfilled with a value
    * Rejected: failed with a rejection reason
    A promise can only go from pending to either fulfilled or rejected. Once it's no longer
    pending its state is frozen. So is its value or reason.
    Photo by Sergiu Bacioiu, CC-BY-NC-2.0, http://www.flickr.com/photos/sergiu_bacioiu/
    5142895618/

    View Slide

  23. var p1 = getValue(); // pending
    We've got our first promise. Let's say it's still pending.

    View Slide

  24. var p1 = getValue(); // still pending
    var p2 = p1.then(function(){
    throw new Error("oops");
    }); // pending
    We know add a handler, resulting in a second promise. Because the first promise is still
    pending, so is the second.The handler we add to p1 throws an error, which means p2 will be
    in a rejected state.

    View Slide

  25. var p1 = getValue(); // fulfilled
    var p2 = p1.then(function(){
    throw new Error("oops");
    }); // rejected
    Now let's say p1 gets fulfilled. The handler we added to p1 throws an error, which means p2
    is in a rejected state.

    View Slide

  26. // p2 is rejected
    var p3 = p2.then(null, function(reason){
    return reason.message;
    }); // p3 is fulfilled
    If we then add a handler for that rejection and return a value, we've put p3 in a fulfilled state.
    Any handler that throws an error puts the returned promise in a rejected state. Any handler
    that returns a value puts it in the fulfilled state.

    View Slide

  27. // p2 is rejected
    var p3 = p2.then(null, function(reason){
    return reason.message;
    }); // technically, p3 is still pending…
    Technically the handler we've added to p2 is invoked in the next turn, which means p3 is still
    pending until the handler has been executed, at which point p3 will be fulfilled.

    View Slide

  28. 18/11/2012 16:42
    Page 1 of 1
    file:///Users/mark/Desktop/promises-aplus.svg
    Promises/A+
    The terminology and examples I've shown so far are compatible with Promises/A+. This is a
    clarification of the original Promises/A specification, aimed at describing how promises work
    in modern day implementations. It's an effort led by Domenic Denicola, who's also worked on
    Q. The specification is a part of this effort, as is defining a test suite that can be used to test
    promise libraries.
    But let's look at using promises with the utilities in Dojo 1.8.

    View Slide

  29. Dojo manages all asynchronous code through promises. It's actually one of the oldest
    libraries to use a promise-like construct in its core.

    View Slide

  30. require([
    "dojo/request"
    ], function(request){
    request("helloworld.txt").then(
    function(text){
    console.log(text);
    }
    );
    });
    For example if you use dojo/request you'll get a promise in return.
    Here we use a handler to get the response value.

    View Slide

  31. require([
    "dojo/request"
    ], function(request){
    request("helloworld.txt").then(
    function(text){
    console.log(text);
    },
    function(reason){
    console.error(reason);
    }
    );
    });
    But we can also add a rejection handler.
    The beauty is that you don't have to read documentation to see which argument to request is
    the success handler, and which is the failure handler. You get a promise, and you know how
    to deal with promises.

    View Slide

  32. function getValue(){
    // ???
    }
    But how would we write our getValue() using Dojo?

    View Slide

  33. require([
    "dojo/Deferred"
    ], function(Deferred){
    function getValue(){
    var dfd = new Deferred();
    setTimeout(function(){
    dfd.resolve("value!");
    }, 1000);
    return dfd.promise;
    }
    });
    dojo/Deferred defines a class that manages a promise for you. Deferreds should be private,
    internal values. You do not return them to the calling code. Instead you return the promise
    that is exposed by the deferred.
    You can fulfill the promise using the resolve() method, or reject() for rejecting it. It's good
    practice to start your function with instantiating the deferred, and returning the promise at
    the very end. This makes it easily recognizable as returning a promise.
    Deferreds themselves aren't part of the Promises/A+ spec, which is OK since they're for
    internal use only. Functions should return promises that *do* adhere to the spec. You don't
    want to return the deferred because calling code shouldn't be able to change state internal to
    your function.

    View Slide

  34. What if you don't know whether a value returns a function or a promise? Use dojo/when!

    View Slide

  35. require(["dojo/when"], function(when){
    when(unknownResult(), function(value){
    });
    });
    when() will immediately invoke our handler if unknownResult returns a non-promise value, or
    pass it to then() if it returns a promise. Note that in the former case, any error thrown by the
    handler is not caught.

    View Slide

  36. require(["dojo/when"], function(when){
    when(unknownResult()).then(function(value){
    });
    });
    when() without handlers will return a promise for the result. It translates promises defined by
    different libraries. In this scenario errors thrown by the handler *are* caught.

    View Slide

  37. That's it for my code examples.

    View Slide

  38. Promise cancelation
    Progress updates
    Reflection
    Tracing & list utilities
    There's some extra functionality in Dojo's promise implementation. It allows you to send
    cancelation messages up the promise chain when results are no longer required. Progress
    updates can be send down the chain without changing the promise state, i.e. allowing you to
    notify calling code of file uploader status. There are reflection methods for determining the
    state of a deferred or promise without having to register handlers, debug helpers, and
    utilities for dealing with multiple promises at once.
    These features aren't specified by Promises/A+, though I'd like to write compatible
    specifications for them.
    Photo by Trey Ratcliff, CC-BY-NC-SA-2.0, http://www.flickr.com/photos/stuckincustoms/
    5896504098/

    View Slide

  39. Use shallow promise
    chains to transform values
    Name your functions as to
    express an algorithm
    Rejections are part of
    your API
    Rethrow unknown
    rejection reasons
    I'd like to conclude with some tips on writing effective promise-based code.
    It's best to keep promise chains shallow. We're still trying to avoid spaghetti code! Name your
    functions so the promise chain remains readable. And remember that throwing errors can
    make for a great API . For instance, it's better to throw a NotFoundError than return a null
    value. In rejection handlers, look for expected errors and handle them, rethrow everything
    else.
    Photo by Chang Liu, CC-BY-2.0, http://www.flickr.com/photos/spaceabstract/7329382340/

    View Slide

  40. I’m @novemberborn or
    [email protected]
    Cheers!
    And that’s all folks. Further questions?
    Photo by Ginny (ginnerobot), http://flickr.com/photos/ginnerobot/2877212845/. CC-BY-SA
    2.0.

    View Slide

  41. STATE
    STATE
    By the way, I'm one of the lead engineers at State, where we’re building a global opinion
    network. If you'd like to work in a great team building great things, come talk to me
    afterwards, or check out https://state.com.

    View Slide

  42. Imagery by
    Sergiu Baciociu
    Alexandre Normand
    Trey Ratcliff
    Chang Liu
    Ginny (ginnerobot)
    Jeff Kubina
    Licensed under Creative Commons Attribution-Non-Commercial-Share Alike 3.0
    http://creativecommons.org/licenses/by-nc-sa/3.0/
    Many, many thanks to the wonderful people on Flickr who licensed their photos under
    Creative Commons.
    Photo by Jeff Kubina, http://flickr.com/photos/kubina/903033693/. CC-BY-SA 2.0.

    View Slide