Promises @ LondonJS

Ad418e3e53e0ba71ba56b7df978671e8?s=47 Mark Wubben
November 19, 2012

Promises @ LondonJS

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

Ad418e3e53e0ba71ba56b7df978671e8?s=128

Mark Wubben

November 19, 2012
Tweet

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/
  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!
  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.
  4. var p = getValue(); What if our function could return

    an object that promises a value will be provided in the future?
  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?
  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?
  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/
  8. Done!

  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/
  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.
  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?
  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.
  13. getValue() // 2 .then(square) // 4 .then(square) // 16 .then(log)

    // --> 16
  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.
  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.
  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.
  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!
  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…
  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?
  20. function getContacts(username){ return findUser(username) .then(lookupContactsForUser); } getContacts("novemberborn") .then(function(contacts){ }); The

    original code, for reference.
  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.
  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/
  23. var p1 = getValue(); // pending We've got our first

    promise. Let's say it's still pending.
  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.
  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.
  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.
  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.
  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. <http://promises-aplus.github.com/promises-spec/>
  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.
  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.
  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.
  32. function getValue(){ // ??? } But how would we write

    our getValue() using Dojo?
  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.
  34. What if you don't know whether a value returns a

    function or a promise? Use dojo/when!
  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.
  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.
  37. That's it for my code examples.

  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/
  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/
  40. I’m @novemberborn or mark@novemberborn.net Cheers! And that’s all folks. Further

    questions? Photo by Ginny (ginnerobot), http://flickr.com/photos/ginnerobot/2877212845/. CC-BY-SA 2.0.
  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.
  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.