Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
The
Promised
Land
by @domenic
in
http://domenic.me
https://github.com/domenic
https://npmjs.org/~domenic
http://slideshare.net/domenicdenicola
THINGS I’M D...
Angular is enlightened
• Like most other client-side frameworks these days, Angular uses promises for
everything async:
• ...
Promises, in General
Web programming is async
• I/O (like XMLHttpRequest, IndexedDB, or waiting for the user to click) takes time
• We have onl...
Async with callbacks
// Ask for permission to show notifications
Notification.requestPermission(function (result) {
// Whe...
Async with events
var request = indexedDB.open("myDatabase");
request.onsuccess = function () {
console.log('opened!');
};...
Async with WTFBBQ
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (this.readyState === this.DONE...
These APIs are a hack
• They are literally the simplest thing that could work.
• But as a replacement for synchronous cont...
Instead of calling a passed callback, return a promise:
var promiseForTemplate = $http.get("template.html");
promiseForTem...
function getPromiseFor5() {
var d = $q.defer();
d.resolve(5);
return d.promise;
}
getPromiseFor5().then(function (v) {
con...
function getPromiseFor5After1Second() {
var d = $q.defer();
setTimeout(function () {
d.resolve(5);
}, 1000);
return d.prom...
promiseForResult.then(onFulfilled, onRejected);
• Only one of onFulfilled or onRejected will be called.
• onFulfilled will...
var transformedPromise = originalPromise.then(onFulfilled, onRejected);
• If the called handler returns a value, transform...
var result;
try {
result = process(getInput());
} catch (ex) {
result = handleError(ex);
}
var resultPromise =
getInputPro...
var result;
try {
result = process(getInput());
} catch (ex) {
result = handleError(ex);
}
var resultPromise =
getInputPro...
Case 1: simple functional transform
var user = getUser();
var userName = user.name;
// becomes
var userNamePromise = getUs...
Case 2: reacting with an exception
var user = getUser();
if (user === null)
throw new Error("null user!");
// becomes
var ...
Case 3: handling an exception
try {
updateUser(data);
} catch (ex) {
console.log("There was an error:", ex);
}
// becomes
...
Case 4: rethrowing an exception
try {
updateUser(data);
} catch (ex) {
throw new Error("Updating user failed. Details: " +...
var name = promptForNewUserName(userId);
updateUser({ id: userId, name: name });
refreshUI();
// becomes
promptForNewUserN...
Key features
In practice, here are some key capabilities promises give you:
• They are guaranteed to always be async.
• Th...
Always async
function getUser(userName, onSuccess, onError) {
if (cache.has(userName)) {
onSuccess(cache.get(userName));
}...
Always async
console.log("1");
getUser("ddenicola", function (user) {
console.log(user.firstName);
});
console.log("2");
/...
Always async
console.log("1");
getUser("ddenicola", function (user) {
console.log(user.firstName);
});
console.log("2");
/...
Always async
function getUser(userName) {
if (cache.has(userName)) {
return $q.when(cache.get(userName));
} else {
return ...
Always async
console.log("1");
getUser("ddenicola“).then(function (user) {
console.log(user.firstName);
});
console.log("2...
getUser("Domenic", function (user) {
getBestFriend(user, function (friend) {
ui.showBestFriend(friend);
});
});
Async “exc...
getUser("Domenic", function (err, user) {
if (err) {
ui.error(err);
} else {
getBestFriend(user, function (err, friend) {
...
getUser("Domenic")
.then(getBestFriend)
.then(ui.showBestFriend)
.catch(ui.error);
Async “exception propagation”
Because promises are first-class objects, you can build simple operations on them instead of tying callbacks
together:
// ...
Building promise abstractions
function timer(promise, ms) {
var deferred = $q.defer();
promise.then(deferred.resolve, defe...
Building promise abstractions
function retry(operation, maxTimes) {
return operation().catch(function (reason) {
if (maxTi...
Promises, in Angular
The digest cycle
function MyController($scope) {
$scope.text = "loading";
$scope.doThing = function () {
var xhr = new XMLHttpRequest();
xh...
function MyController($scope) {
$scope.text = "loading";
$scope.doThing = function () {
jQuery.get("somefile.json").then(f...
function MyController($scope) {
$scope.text = "loading";
$scope.doThing = function () {
jQuery.get("somefile.json").then(f...
function MyController($scope, $http) {
$scope.text = "loading";
$scope.doThing = function () {
$http.get("somefile.json")....
Useful things
• $q.all([promise1, promise2, promise3]).then(function (threeElements) { … });
• $q.all({ a: promiseA, b: pr...
Gotchas
• Issue 7992: catching thrown errors causes them to be logged anyway
• Writing reusable libraries that vend $q pro...
Thanks!
promisesaplus.com
@promisesaplus
Upcoming SlideShare
Loading in …5
×

The Promised Land (in Angular)

22,517 views

Published on

Promises are a popular pattern for asynchronous operations in JavaScript, existing in some form in every client-side framework in widespread use today. We'll give a conceptual and practical intro to promises in general, before moving on to talking about how they fit into Angular. If you've ever wondered what exactly $q was about, this is the place to learn!

Published in: Technology, Education, Business
  • Great points about advantages of promises! However, I'd like to note that some of the recommendations at the end are outdated with regards to AngularJS 1.2. For example, $q now supports 'finally' instead of 'always' to be closer aligned to Q. Most importantly, the async behavior has been fixed to always fire, even if there is no upcoming digest cycle.

    From Igor Minar:
    https://github.com/angular/angular.js/issues/2881#issuecomment-39017671

    'In any case, the Angular-specfic slides in the slide deck you are referring to are outdated or plain wrong. Unfortunately Domenic misunderstood the reasoning behind some of the $q design decisions and didn't check with us before publishing the slides.'
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

The Promised Land (in Angular)

  1. 1. The Promised Land by @domenic in
  2. 2. http://domenic.me https://github.com/domenic https://npmjs.org/~domenic http://slideshare.net/domenicdenicola THINGS I’M DOING  The Promises/A+ and ES6 promise specs  Working on Q  The Extensible Web Manifesto Domenic Denicola
  3. 3. Angular is enlightened • Like most other client-side frameworks these days, Angular uses promises for everything async: • $timeout • $http + response interceptors • $resource • $routeProvider.when • Its built-in promise library, $q, is pretty good. • But first, let’s take a step back and start from the beginning.
  4. 4. Promises, in General
  5. 5. Web programming is async • I/O (like XMLHttpRequest, IndexedDB, or waiting for the user to click) takes time • We have only a single thread • We don’t want to freeze the tab while we do I/O • So we tell the browser: • Go do your I/O • When you’re done, run this code • In the meantime, let’s move on to some other code
  6. 6. Async with callbacks // Ask for permission to show notifications Notification.requestPermission(function (result) { // When the user clicks yes or no, this code runs. if (result === 'denied') { console.log('user clicked no'); } else { console.log('permission granted!'); } }); // But in the meantime, this code continues to run. console.log("Waiting for the user...");
  7. 7. Async with events var request = indexedDB.open("myDatabase"); request.onsuccess = function () { console.log('opened!'); }; request.onerror = function () { console.log('failed'); }; console.log("This code runs before either of those");
  8. 8. Async with WTFBBQ var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { if (this.readyState === this.DONE) { if (this.status === 200) { console.log("got the data!" + this.responseText); } else { console.log("an error happened!"); } } }; xhr.open("GET", "somefile.json"); xhr.send();
  9. 9. These APIs are a hack • They are literally the simplest thing that could work. • But as a replacement for synchronous control flow, they suck. • There’s no consistency. • There’s no guarantees. • We lose the flow of our code writing callbacks that tie together other callbacks. • We lose the stack-unwinding semantics of exceptions, forcing us to handle errors explicitly at every step.
  10. 10. Instead of calling a passed callback, return a promise: var promiseForTemplate = $http.get("template.html"); promiseForTemplate.then( function (template) { // use template }, function (err) { // couldn’t get the template }); Promises are the right abstraction
  11. 11. function getPromiseFor5() { var d = $q.defer(); d.resolve(5); return d.promise; } getPromiseFor5().then(function (v) { console.log('this will be 5: ' + v); }); Creating a promise
  12. 12. function getPromiseFor5After1Second() { var d = $q.defer(); setTimeout(function () { d.resolve(5); }, 1000); return d.promise; } getPromiseFor5After1Second().then(function (v) { // this code only gets run after one second console.log('this will be 5: ' + v); }); Creating a promise (more advanced)
  13. 13. promiseForResult.then(onFulfilled, onRejected); • Only one of onFulfilled or onRejected will be called. • onFulfilled will be called with a single fulfillment value (⇔ return value). • onRejected will be called with a single rejection reason (⇔ thrown exception). • If the promise is already settled, the handlers will still be called once you attach them. • The handlers will always be called asynchronously. Promise guarantees
  14. 14. var transformedPromise = originalPromise.then(onFulfilled, onRejected); • If the called handler returns a value, transformedPromise will be resolved with that value: • If the returned value is a promise, we adopt its state. • Otherwise, transformedPromise is fulfilled with that value. • If the called handler throws an exception, transformedPromise will be rejected with that exception. Promises can be chained
  15. 15. var result; try { result = process(getInput()); } catch (ex) { result = handleError(ex); } var resultPromise = getInputPromise() .then(processAsync) .then(undefined, handleErrorAsync); The sync ⇔ async parallel
  16. 16. var result; try { result = process(getInput()); } catch (ex) { result = handleError(ex); } var resultPromise = getInputPromise() .then(processAsync) .catch(handleErrorAsync); The sync ⇔ async parallel
  17. 17. Case 1: simple functional transform var user = getUser(); var userName = user.name; // becomes var userNamePromise = getUser().then(function (user) { return user.name; });
  18. 18. Case 2: reacting with an exception var user = getUser(); if (user === null) throw new Error("null user!"); // becomes var userPromise = getUser().then(function (user) { if (user === null) throw new Error("null user!"); return user; });
  19. 19. Case 3: handling an exception try { updateUser(data); } catch (ex) { console.log("There was an error:", ex); } // becomes var updatePromise = updateUser(data).catch(function (ex) { console.log("There was an error:", ex); });
  20. 20. Case 4: rethrowing an exception try { updateUser(data); } catch (ex) { throw new Error("Updating user failed. Details: " + ex.message); } // becomes var updatePromise = updateUser(data).catch(function (ex) { throw new Error("Updating user failed. Details: " + ex.message); });
  21. 21. var name = promptForNewUserName(userId); updateUser({ id: userId, name: name }); refreshUI(); // becomes promptForNewUserName(userId) .then(function (name) { return updateUser({ id: userId, name: name }); }) .then(refreshUI); Bonus async case: waiting
  22. 22. Key features In practice, here are some key capabilities promises give you: • They are guaranteed to always be async. • They provide an asynchronous analog of exception propagation. • Because they are first-class objects, you can combine them easily and powerfully. • They allow easy creation of reusable abstractions.
  23. 23. Always async function getUser(userName, onSuccess, onError) { if (cache.has(userName)) { onSuccess(cache.get(userName)); } else { $.ajax("/user?" + userName, { success: onSuccess, error: onError }); } }
  24. 24. Always async console.log("1"); getUser("ddenicola", function (user) { console.log(user.firstName); }); console.log("2"); // 1, 2, Domenic
  25. 25. Always async console.log("1"); getUser("ddenicola", function (user) { console.log(user.firstName); }); console.log("2"); // 1, Domenic, 2
  26. 26. Always async function getUser(userName) { if (cache.has(userName)) { return $q.when(cache.get(userName)); } else { return $http.get("/user?" + userName); } }
  27. 27. Always async console.log("1"); getUser("ddenicola“).then(function (user) { console.log(user.firstName); }); console.log("2"); // 1, 2, Domenic (every time!)
  28. 28. getUser("Domenic", function (user) { getBestFriend(user, function (friend) { ui.showBestFriend(friend); }); }); Async “exception propagation”
  29. 29. getUser("Domenic", function (err, user) { if (err) { ui.error(err); } else { getBestFriend(user, function (err, friend) { if (err) { ui.error(err); } else { ui.showBestFriend(friend, function (err, friend) { if (err) { ui.error(err); } }); } }); } }); Async “exception propagation”
  30. 30. getUser("Domenic") .then(getBestFriend) .then(ui.showBestFriend) .catch(ui.error); Async “exception propagation”
  31. 31. Because promises are first-class objects, you can build simple operations on them instead of tying callbacks together: // Fulfills with an array of results when both fulfill, or rejects if either reject all([getUserData(), getCompanyData()]); // Fulfills with single result as soon as either fulfills, or rejects if both reject any([storeDataOnServer1(), storeDataOnServer2()]); // If writeFile accepts promises as arguments, and readFile returns one: writeFile("dest.txt", readFile("source.txt")); Promises as first-class objects
  32. 32. Building promise abstractions function timer(promise, ms) { var deferred = $q.defer(); promise.then(deferred.resolve, deferred.reject); setTimeout(function () { deferred.reject(new Error("oops timed out")); }, ms); return deferred.promise; } function httpGetWithTimer(url, ms) { return timer($http.get(url), ms); }
  33. 33. Building promise abstractions function retry(operation, maxTimes) { return operation().catch(function (reason) { if (maxTimes === 0) { throw reason; } return retry(operation, maxTimes - 1); }); } function httpGetWithRetry(url, maxTimes) { return retry(function () { return $http.get(url); }, maxTimes); }
  34. 34. Promises, in Angular
  35. 35. The digest cycle
  36. 36. function MyController($scope) { $scope.text = "loading"; $scope.doThing = function () { var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { if (this.readyState === this.DONE && this.status === 200) { $scope.text = this.responseText; } }; xhr.open("GET", "somefile.json"); xhr.send(); }; } // Doesn’t work, because the callback function is outside the digest cycle!
  37. 37. function MyController($scope) { $scope.text = "loading"; $scope.doThing = function () { jQuery.get("somefile.json").then(function (responseText) { $scope.text = responseText; }); }; } // Still doesn't work: same problem
  38. 38. function MyController($scope) { $scope.text = "loading"; $scope.doThing = function () { jQuery.get("somefile.json").then(function (responseText) { $scope.apply(function () { $scope.text = responseText; }); }); }; } // Works, but WTF
  39. 39. function MyController($scope, $http) { $scope.text = "loading"; $scope.doThing = function () { $http.get("somefile.json").then(function (response) { $scope.text = response.data; }); }; } // Works! Angular’s promises are integrated into the digest cycle
  40. 40. Useful things • $q.all([promise1, promise2, promise3]).then(function (threeElements) { … }); • $q.all({ a: promiseA, b: promise }).then(function (twoProperties) { … }); • Progress callbacks: • deferred.notify(value) • promise.then(undefined, undefined, onProgress) • But, use sparingly, and be careful • $q.when(otherThenable), e.g. for jQuery “promises” • promise.finally(function () { // happens on either success or failure });
  41. 41. Gotchas • Issue 7992: catching thrown errors causes them to be logged anyway • Writing reusable libraries that vend $q promises is hard • $q is coupled to Angular’s dependency injection framework • You have to create an Angular module, which has limited audience • Angular promises are not as full-featured as other libraries: • Check out Q or Bluebird • But to get the digest-cycle magic, you need qPromise.finally($scope.apply). • Deferreds are kind of lame compared to the ES6 Promise constructor. • Progress callbacks are problematic.
  42. 42. Thanks! promisesaplus.com @promisesaplus

×