I'm Postal for Promises in Angular

6,358 views

Published on

A talk delivered at ng-conf 2014, in Salt Lake City. Covers both the principles of why the Promise pattern is a vast improvement over callbacks for developer productivity, plus the specific ways that Promises are implemented in AngularJS.

Published in: Technology, Business
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
6,358
On SlideShare
0
From Embeds
0
Number of Embeds
3,297
Actions
Shares
0
Downloads
45
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

I'm Postal for Promises in Angular

  1. 1. I’m POSTAL For PROMISES @ 2014 by: Christian Lilley about.me/xml Thursday, January 16, 14 @xmlilley
  2. 2. Intro to Promises on my blog: christianlilley.com Thursday, January 16, 14 We're going to move really fast today, but if you want to go in depth, I’ve got some training slides on my blog. • If you're at ng-conf, chances are, you've at least SEEN promises. They're those weird things you have to extract your data from when they come out of the $http service, right? So, why do they exist?
  3. 3. UNCERTAINTY ASYNC Thursday, January 16, 14 • If you've heard of them, you've probably heard that they're about async. Which is true. But that significantly understates their usefulness. Really, they’re designed in a way that lets them be about the bigger, more general idea of uncertainty. About being able to crystallize uncertainty about time, space, user-based uncertainty or almost anything, into a nice neat package, put a bow on it, and give it a super-useful API that provides us with a gorgeous interaction pattern, that allows you to reason about and make DECISIONS about uncertainty throughout your application.
  4. 4. BRAINS!!!! Thursday, January 16, 14 • and they're about helping you make the most of your brain when dealing with that uncertainty. Promises make engineer brains work better. Zombie approved.
  5. 5. Thursday, January 16, 14 But even though promises ARE about way more than just async, the truth is its what justifies their existence, so let’s talk async. In the beginning was the Word, and the Word was Callback, and the Callback was...
  6. 6. meh. Thursday, January 16, 14
  7. 7. Callbacks: Strengths They make Non-Blocking I/O possible. You know how to use them. </strengths> Thursday, January 16, 14 Look, callbacks do the job: they make non-blocking I/O possible, and everybody knows how to use them. But...
  8. 8. Callbacks/CPS NOW, NOW, NOW, NOW, NOW, NOW, NOW No API for further interaction No API for crossing contexts Doesn’t Scale: works fine for 1, horrible for more Monolithic Handlers that break your Thursday, January 16, 14 ... they don't scale. They lead us towards huge, monolithic handler functions that try to do everything all at once, and an attitude towards async that it's something you want to do as little of as possible and get it out of the way.
  9. 9. step1(value1,  function  (output)  {        step2(output1,  function(output2)  {                step3(output2,  function(output3)  {                        step4(output3,  function(output4)  {                                //  Do  something  with  value4,                                      quick,  before  it                                                                disappears!!!                        });                });        }); }); Thursday, January 16, 14 When you start trying to stack a complex operation with callbacks, things get ugly, and nested, and complex, and unreadable really quickly. We haven’t even put anything into these handler functions yet, and they’re already a mess. See this shape forming here on the left? You know what this shape is called?
  10. 10. PYRAMID OF DOOOM!!!! Thursday, January 16, 14 In the PYRAMID of DoOOOOM, we need to do everything RIGHT NOW, because in a second, it's going to be gone. Forget about coming back and picking up later where you left off. And forget about that code being intuitive and readable and clear and maintainable.
  11. 11. var  step1Started  =  true; step1(value1,  callback1); var  step1State  =  false; function  callback1(data)  {    step1State  =  true;    step1Data  =  data;    doSomethingWithStep1Data(); } .... later .... if (step1State && step1Data) { doSomethingElseWithStep1(); } Thursday, January 16, 14 •And if you do invade the PYRAMID of DoOOOOM, and try to implement something that does let you leave off in the middle of the pyramid and come back later, you’re going to start building boilerplate like this, caching state variables and outputs, and adding either timers or events to trigger downstream actions. •It works, but good luck with interop with other modules you didn't write, and good luck passing sequenced instructions back and forth between scopes that don't all exist yet, and hence can't listen for events.
  12. 12. step1(value1,  callback1); step2(value2,  callback2); var  step1State  =  false; function  callback1(data)  {    step1State  =  true;    step1Data  =  data;    doSomethingWithStep1Data(); } .... timer... later .... if (step1State && step1Data) { doSomethingElseWithStep1(); } var  step2State  =  false; function  callback2(data)  {    step2State  =  true;    step2Data  =  data;    doSomethingWithStep2Data(); } .... timer... later .... if (step2State && step2Data) { doSomethingElseWithStep2(); } step3(value3,  callback3); step4(value4,  callback4); var  step3State  =  false; function  callback3(data)  {    step3State  =  true;    step3Data  =  data;    doSomethingWithStep3Data(); } .... timer ... later .... if (step3State && step3Data) { doSomethingElseWithStep3(); } var  step4State  =  false; function  callback4(data)  {    step4State  =  true;    step4Data  =  data;    doSomethingWithStep4Data(); } .... timer ... later .... if (step4State && step4Data) { doSomethingElseWithStep4(); } Thursday, January 16, 14 And if you want try that with all four of the tasks from that PYRAMID of DoOOOOM, you get this. Mind you, all this junk doesn’t even include any error or success handlers yet. This is JUST what you need to have to be able to talk reasonably about whether these four async functions have fired yet, whether they’ve returned yet, and whether you can do anything useful with their data yet. And NONE of this code is making your application do anything yet. It’s all just a bunch of imperative-style housekeeping that’s hard enough to keep track of and maintain, and even harder to reason about and extend.
  13. 13. Promises to the rescue. Thursday, January 16, 14
  14. 14. Thursday, January 16, 14 Let's be clear: promises are not a technical innovation in the javascript execution engine. They don't make anything happen more quickly. They don't execute async operations with different native code. Promises are all about the brains of programmers, the same reason we do automated testing & TDD, talk about principles like 'single responsibility' and 'least surprise'. The promise pattern is about making engineers more efficient, enabling them to actually reason about async sensibly. It allows you to look at async stuff and uncertain stuff, visualize it, and write efficient code AS IF it were predictable, consecutive synchronous operations. And to link those operations together in virtuous cycles or flows.
  15. 15. And again, what promises are not: Thursday, January 16, 14 • So, if all that’s true, the naiive understanding of promises needs to go out the door:
  16. 16. action(target, callback, error) !== action(target).then( callback, error) Thursday, January 16, 14 Promises are NOT simply a syntax replacement for callbacks. There wouldn't be any point in Promises if that was all there was to it.
  17. 17. Promises are just a “replacement for callbacks”... ...if internal combustion was just a “replacement for horses.” Thursday, January 16, 14 Used right, Promises are a qualitative change, a fundamental shift in how we compose and plan for uncertainty and sequencing. So how about an example.
  18. 18. IMPERATIVE -------> DECLARATIVE Thursday, January 16, 14 How about if, instead of all that boilerplate I just showed you, what if you could just DECLARE what you want to have happen? That you want a bunch of uncertain things to happen in a certain sequence, based on some other uncertain things?
  19. 19. step1. then(step2). then(step3). then(step4) Thursday, January 16, 14 How about that? Welcome to declarative async and uncertainty with promises.
  20. 20. step1(value1,  callback1); step2(value2,  callback2); var  step1State  =  false; function  callback1(data)  {    step1State  =  true;    step1Data  =  data;    doSomethingWithStep1Data(); } .... timer... later .... if (step1State && step1Data) { doSomethingElseWithStep1(); } var  step2State  =  false; function  callback2(data)  {    step2State  =  true;    step2Data  =  data;    doSomethingWithStep2Data(); } .... timer... later .... if (step2State && step2Data) { doSomethingElseWithStep2(); } step3(value3,  callback3); step4(value4,  callback4); var  step3State  =  false; function  callback3(data)  {    step3State  =  true;    step3Data  =  data;    doSomethingWithStep3Data(); } .... timer ... later .... if (step3State && step3Data) { doSomethingElseWithStep3(); } var  step4State  =  false; function  callback4(data)  {    step4State  =  true;    step4Data  =  data;    doSomethingWithStep4Data(); } .... timer ... later .... if (step4State && step4Data) { doSomethingElseWithStep4(); } Thursday, January 16, 14 Instead of you writing all this boilerplate just to sequence 4 async operations across your application, and be able to come back and interact with them later...
  21. 21. step1. then(step2). then(step3). then(step4) Thursday, January 16, 14 ... all you need is this. This is how the promise pattern sequences things. It knows when it’s allowed to move forward between steps, and keeps everything in order.
  22. 22. Or, decompose them... Thursday, January 16, 14
  23. 23. as1 = step1(); as2 = as1.then(step2); as3 = as2.then(step3); as4 = as3.then(step4); Thursday, January 16, 14 And we can decompose that chain of operations incredibly easily, just by storing a reference to the promises for each stage of that sequence. The promises are the variables on the left. These actions will still fire in the same order as the previous example, but now we can start additional actions, later, whenever and wherever we want, from any point in the middle of the sequence, using the data that comes out of those operations.
  24. 24. $HTTP Thursday, January 16, 14 It’s like having DVR, for your async operations. And we can even pass these references into other contexts of the application, without those contexts needing to know anything at all about the process that generated the promise.
  25. 25. And if you need parallelism... Thursday, January 16, 14 What if you want to have three of them run in parallel instead of in sequence, and have the fourth one kick in only when all three are done?
  26. 26. as4 = $Q.all([as1, as2, as3]) .then(step4) Thursday, January 16, 14 Either, you write all the imperative boilerplate and state variables and other junk we saw before, THEN set a timer to check every few ms to see if they’re all done yet, or use events if you’re lucky... all that OR you do this one line that replaces all of that. That’s composition with promises. That’s the pattern they allow you to work with. So, if that’s the overall pattern, how should we understand the component promise objects themselves?
  27. 27. So, in a nutshell, a promise object itself is... Thursday, January 16, 14 So... I promised a certain metaphor in my talk title, even though we don’t have time for all the details here. But at root, understand that a promise is basically just a mailbox.
  28. 28. Thursday, January 16, 14 It’s a special, standardized repository where you can stash the data that outputs from an operation, it has a property representing the completion and success state of the operation, and it has utilities for leveraging that knowledge into further operations. And all your functions can have a key to this mailbox, so they know what’s going on. You can even pick this mailbox up and carry it around with you by reference into different contexts of your app, providing your components with all the benefits of async, without needing to understand implementation.
  29. 29. If the Promise is the mailbox, then the Deferred: Is the Postman. Thursday, January 16, 14 Then, there’s this thing called the deferred. Lots of folks get Promises but have trouble with the Deferred object. But it’s really straightforward once you think of the promise as a mailbox. The deferred is simply the postman.
  30. 30. No, not that one, thank God... Thursday, January 16, 14 No, not THAT endless tribute to Kevin Costner’s vanity, thank god.
  31. 31. If the Promise is the mailbox, then the Deferred: Is the Postman. The Postman is the only one who’s allowed to: Assign you a new mailbox. (ie. create a promise) Put new packages into the mailbox. (resolve the promise) Raise the little flag on the side. (set state of the promise) Thursday, January 16, 14 Just like with actual postal workers the postman is the only one who’s allowed to: Assign you a new P.O. Box, put new packages into the mailbox, and, inverting the real life example, let’s pretend the little flag on the side of the mailbox is used to show the box has incoming mail, not outgoing mail. With the deferred, you’re the postman, and you can create and administer the mailbox. Without the deferred, you’re a customer, a promise-consumer.
  32. 32. But it’s the pattern that matters most... Thursday, January 16, 14 That’s the main idea. But the pattern of use is more significant than the individual promise objects themselves. Alone, they’re just syntactic sugar.
  33. 33. DECLARATIVE, NOT IMPERATIVE. A UNIVERSAL API for uncertainty about time & place. An abstraction for any operation that has happened or will happen. A proxy for a remote object, which allows you to abstract away all the complexity of going and getting it. A zero-boilerplate API for interacting with the state, data, timing & sequence of complex operations. Thursday, January 16, 14 Together, they can change your whole approach to composition, and they can sure as hell reduce the amount of code you write.
  34. 34. NOT JUST FOR ASYNC Thursday, January 16, 14 Now, remember, PROMISES ARE NOT JUST FOR ASYNC.
  35. 35. userRegistered.then() tutorialComplete.then() purchaseSuccessful.then() profileLocated.then() etc... Thursday, January 16, 14 Time and place uncertainty are certainly the conventional context for promises. But there are other kinds, especially user-related uncertainty. A given AJAX request is actually semi-predictable, within a range. Users, though, are totally unpredictable. You have no idea when - or if - your user will get around to doing certain things, or in what order. Take advantage of promises as a way to encapsulate and abstract this kind of sequencing uncertainty as well, even though it’s not execution engine asynchronousness in the way we mean that term. But using promises, you can build custom async interfaces around user uncertainty.
  36. 36. Thursday, January 16, 14 Which brings us to Angular. Angular is an example of what happens when you start thinking in promises, structurally. When you bake them right into the fabric of the application and use them as a way to model process flows and make sure that the right things happen in the right place at the right time, so the different parts of your application can hand-off those flows seamlessly, and customize them. If you think that Misko and Vojta and Igor and Brian and Brad and all the other cats on the angular team are pretty smart, if you're HERE, in other words, then follow their example and get way into promises.
  37. 37. 1. Consume promises returned by essential built-in Services. $http $timeout $interval Thursday, January 16, 14 Here’s sort-of an 8-step program to using promises in Angular. Step 1: When you get started with promises, you’ll just consume them, from built-in services that return promises. $http.... and very notably $timeout and $interval. Angular has gone to the trouble to make all of the most fundamental async functions into promise generators, so that Promises can be the seamless common API for all async.
  38. 38. var promise= $http.get(‘URL’) promise.then(doSomething); promise.then(doThing2); promise.then(doThing∞); Thursday, January 16, 14 It’s as simple as this: if $http returns a promise, all you have to do is store a reference to that promise. Forever after, for as long as you hold that reference, you can run other operations against it.
  39. 39. 2. $resource calls now return promises (in 1.2), & $resource objects *have* one. (Aren’t one. Have one.) Thursday, January 16, 14 Step 2, when you’re ready to get a little tricky, try $resource. (The implementation of $resource has shifted quite a bit in the past, but here’s the 1.2 skinny:) $resource works a little differently than $http, but is nonetheless promise-style processing, via a useful, but perhaps confusing, pattern: $http.get() returns a simple *promise*. But calling the initial .get() method on a $resource type that you’ve defined, returns a... *resource object*, which itself *has* a promise. It Isn't one. But it has one, as a property.
  40. 40. var bob = resourceType.get({params}) // returns... Resource {$promise: Object, $resolved: true, $get: function, $save: function, $query: function…} // after that... bob.$promise.then(doStuff) Thursday, January 16, 14 * To chain from that $resource with .then(), just use resourceObject.$promise • Note that the reference is circular. So, if you unwrap a $resource's $promise object, what you get back is the original $resource. Which, of course, still has a $promise. Which you can unwrap. So, the $resource has a $promise which has a $resource which has a $promise...
  41. 41. credit: substack/stream-handbook Thursday, January 16, 14 Truly, it’s turtles all the way down. Complicating things further, the other instance methods on a $resources, like $save(), they don’t modify that original promise, instead they return a new bare $promise for the state of the operation, rather than the $resource itself.
  42. 42. 3. $route:resolve (Integrating the promise architecture so cleanly that controllers can be fully ignorant of it.) Thursday, January 16, 14 Step 3 is such a big, worthwhile feature that one of the release candidates for the version of Angular it debuted in was named 'promise-resolution'.
  43. 43. $routeProvider .when('/dashboard', { templateUrl: 'URL', controller: 'Ctrl', resolve: {myData: ['myService', function(myService) {return myService.get('URL', {params}).$promise }] } Thursday, January 16, 14 Using the :resolve property in the route, you get several huge benefits that you just don’t want to be operating without. 1. the route won’t change until your data has been obtained, and you have everything you need to render the view. No flicker, nothing looks broken. 2. Your controller gets to be totally, completely ignorant of implementation. It doesn’t need to know what a promise is. It doesn’t need to know how to unwrap it, or check the success of the operation. $routeProvider does all of that for you. All the controller gets is a data object, injected at creation time, so it gets to be totally implementation independent.
  44. 44. angular.module('myApp') .controller('Ctrl', function(myData) { // do stuff with myData $scope.myData = myData; }) Thursday, January 16, 14 And so then, in the controller, all you have to do is inject the name of the property, and you can do whatever you want with it, like bind it to the $scope.
  45. 45. 4. Note: Automatic unwrapping in views: GONE in 1.2 (Too much magic.) Thursday, January 16, 14 Step 4 is just: be aware of a big recent change in Angular 1.2. For a little while there, we could place a promise object on the $scope, and angular would automagically extract the value from it and inject *that* into the view. Yadda yadda yadda, reading the repo, it seems it was just too much magic for people. It made consumers of promises less aware of how they worked and how to use them. It was also slow. So, that feature, which you might see in old documentation on the web, is gone now.
  46. 46. Alternatives: Either inject your promises via :resolve, so they’re autounwrapped, or... promise.then(function(value) {$scope.value = value}) Thursday, January 16, 14 Your Alternatives are clear and easy: Either inject promises with the :resolve property if you like magical unwrapping, or just unwrap ‘em yourself.
  47. 47. 5. Create your own promises with $Q. (Just like Q.js, only smaller.) Thursday, January 16, 14 Step 5, start creating your own promises with $Q. Check out my detailed slides for more on how to do that.
  48. 48. 6. Third-party libraries like angular-ui-bootstrap are built on promises, too. (Great example of workflow: ng-modal.) Thursday, January 16, 14 Step 6: Start noticing how many Third-Party Implementations, like UI-Bootstrap Modal also take advantage of promise flows.
  49. 49. $scope.modalOpen = function () { var modalConfigs = { templateUrl: ..., controller: ..., scope: $scope, resolve: { specialData: function () {return $http.get(‘/somedata’);} } } var modalInstance = $modal.open(modalConfigs); modalInstance.result.then(handlerFunc); }; // Inside the modalInstance: result.resolve(); Thursday, January 16, 14 When you define a modal instance, which carries a promise, you immediately define a handler, with modalInstance.result.then(). Before we’ve even had time to do anything with that modal, we’ve already created a handler for whatever we’re going to eventually return from the modal dialog as the result object. We don’t know when or if there will be a return. But we’re ready for it, we can reason about it. And we can easily hand off to some other consumer as well. ALSO: UI-Modal uses its own :resolve property, just like the Angular router does, to ensure that the view won’t be rendered until the data is ready.
  50. 50. 7. Wrap a promise in an Angular Service, and you have a *Lazy Promise*. (With lazy promises, time is now fully irrelevant. Point and shoot.) Thursday, January 16, 14 Step 7: promises that you can reference without needing to care if they even exist yet, let alone if they’ve resolved. Normally, you need to know that a promise has been instantiated before you start interacting with it. But because Angular services are instantiated lazily, as needed, and persist throughout application lifecycle, a Promise wrapped in a service can be referenced at any time, from any location, provided you inject the service. The caller of the promise need not know or care anything about the implementation.
  51. 51. function getDivision(divisionName) { if (!divisionObjects[divisionName]) { divisionObjects[divisionName] = $http.get(baseUrl + divisionName) .error(function(data, status) { console.error('getDivision failed, error:' + status) }); } else { return divisionObjects[divisionName]; } } Thursday, January 16, 14 • Outside of Angular (or if you want multiple promises in one service) you can do the same thing, using a simple getter function that will return an existing promise if available, or create a new one if not.
  52. 52. 8. Interceptors (and the rest of the $http lifecycle) (Great for loaders, other inline transformations.) Thursday, January 16, 14 Step 8: is Master-level! HTTP interceptors are these awesome things that allow you to mess around with every single AJAX operation that goes in or out of your application, all from a single location. And they’re promised based... in BOTH DIRECTIONS, in and out, request and response. Promises, again, aren't just something for async output. They’re part of the modeling of the $http cycle from the very beginning, and you can thus chain even before an async operation kicks off, perhaps executing another async operation before the main one is even allowed to start.
  53. 53. $httpProvider.interceptors.push(function($q, $rootScope) { return { 'request': function(config) { $rootScope.$broadcast('asyncLoadStart'); return config || $q.when(config); }, 'requestError': function(rejection) { $rootScope.$broadcast('asyncLoadEnd'); return $q.reject(rejection); }, 'response': function(response) { $rootScope.$broadcast('asyncLoadEnd'); return response || $q.when(response); }, 'responseError': function(rejection) { $rootScope.$broadcast('asyncLoadEnd'); return $q.reject(rejection); } }; }) Thursday, January 16, 14 This example is stripped bare and over-simplified, just so it fits on-screen. It shows a way to trigger an automatic, universal loader screen for your app. With promises, we’re able to interact with the stream of low-level instructions in Angular’s infrastructure, and we can even conveniently pause the stream, while we wait for something to happen, simply by returning a promise that hasn’t resolved yet. This allows us to: do validation, or checking that a user session is still open... whatever... before we proceed. By composing and inserting a single promise object, in the right state, you get that much control, right at the heart of Angular’s operations.
  54. 54. Bonus Detail: terminate an $http request... if a promise resolves (Using the promise API for mid-stream interventions, not just outputs...) Thursday, January 16, 14 Bonus detail: check out the ‘timeout’ property of an $http request’s config object. It shows us yet another model for the use of promises, where instead of something starting when a promise resolves, we can terminate something when a promise resolves. Maybe it’s a parallel $http request, and we want to stop whenever one or the other returns. Or maybe it’s simply a promise indicating an option that was set somewhere. Either way, Angular knows what to do with it, because a promise is our universal API for all kinds of uncertainty that we want to conveniently make decisions about.
  55. 55. Best Practices Use SOA: goes with promises like peanut butter with chocolate. Think of promises as the beginning of a lifecycle or flow or stream, not just a one-stop ride to a callback. Decompose handler functions, as promises make it easy to chain them. Thursday, January 16, 14
  56. 56. And lest you *still* thought Promises weren’t really mainstream... Thursday, January 16, 14
  57. 57. Thursday, January 16, 14 • Previously, promises had been a library feature. Now, native promises in the browser are not only part of the ECMAscript 6 spec, they’re in release, in Chrome 32, as of this very week. Syntax is a little different than the Promises/A+ spec, but I think in a good way. • This means that, even though other browsers are going to lag, we can unify around an API for Promises, and unify around Promises as our key API for uncertainty, and you can invest in learning them knowing that they're not going anywhere. • And Not only are they not going anywhere, you're going to HAVE to use them, even outside Angular. New async DOM APIs will return promises! They'll be unavoidable. Angular ... AND YOU... just got there ahead of the curve.
  58. 58. Thank You! about.me/XML Thursday, January 16, 14

×