I have to admit I found the concept of Promises difficult to grasp at first; once I understood, I’ve been looking for a good way to explain it to others. This article is my attempt to make Promises easy to understand through a simple metaphor.
Imagine you’ve entered a crowded restaurant. You force your way to the maître d’ and request a table, and are told there is a wait. “We’ll call your name when the table is ready,” she says. This is the equivalent of a “callback” function – the server (a restaurant server in this case) will call you (the client) back when your request is complete. Inevitably half the people in the room are named “Bob” and a crowded confusion ensues.
Now this callback is necessary because the request is asynchronous. If the table was available immediately, the request could be handled synchronously and you would be shown to your seat right away. But in the crowded restaurant, that just wouldn’t work! Imagine asking for a table and the server just freezes in place until it’s ready. Awkward!! Plus, many servers would be needed to stand around waiting with each dining party, like threads in a computer waiting for network requests to complete. It’s just too expensive to tie up all those resources waiting for something to happen, especially that slow party in the corner who are on their 3rd cup of coffee. That’s why JavaScript uses asynchronous requests.
However the callback isn’t ideal either. Recently, restaurants have started handing out pagers to waiting patrons – you know, the ones that look like a combination of a coaster and a hockey puck. This frees up would-be diners to move around, shop next door, etc. knowing the pager will blink when the request has been fulfilled. A “promise” is the JavaScript equivalent of this little pager. Instead of calling you back directly, a service gives you an object that will do the callback, and the loose coupling makes life easier.
One nice thing about this is that you can hand the object to someone else, or give them a new object to wait on. Perhaps my wife is at the restaurant and there’s a used book store nearby – I guarantee she will pass the time looking through the stacks. Rather than chase her down when the server calls out, I could give her the hockey puck so she’ll be notified, or give her a new pager and beep that in response to the restaurant pager going off. Similarly, in JavaScript I could pass a promise object to some other code, or make a new promise to hand out. Promises are much more flexible than regular callbacks! (Hint: she was in the science fiction section.)
Now let’s imagine that the maître d’ has a bad day and collapses from exhaustion before getting your table – a real world example of server failure! In an ideal world, your pager would now blink a different color to let you know that your request failed and you need to order take-out instead. Promises can do that – they can indicate success or failure. An ideal restaurant pager would also show status: “that slow party has finally left and we’re cleaning your table now.” JavaScript promises can do that too!
Finally, imagine that your dining party is really large, so much that you won’t all fit at one table. So you request three tables. But you want to all sit down at the same time, so you really want the beeper to go off only when all three tables are ready. JavaScript promises can do that as well – they’re very flexible!
Of course the story wouldn’t be complete without a code example! Here’s a code snippet from an AngularJS service I wrote; this is calling a RESTful web service in SharePoint.
function readListItemByTitle(listName, title) { var siteUrl = getSiteUrl(); var deferred = $q.defer(); $http.get(siteUrl + "/_api/web/lists/GetByTitle(" + listName + ")/items/?$filter='Title eq " + title + "'", getGetConfig()) .then(function (response) { if (response.data.d.results.length <= 0) { deferred.reject(404); } else { deferred.resolve(response.data.d.results[0]); } }) .catch(function (response) { deferred.reject(response.status); }); return deferred.promise; }
This code reads a SharePoint list item based on its title. The important thing to notice is that it’s an asynchronous call; when the $http.get()
function returns, it does not return a result because the request is still in process; instead it returns a promise. The .then()
function runs if the promise was successful (the restaurant pager beeps); at that time we receive the data in a response. If there’s a problem, then the .catch()
function runs.
Not only is this code consuming a promise from $http
, it’s also creating a new promise for its caller. The code uses the Angular $q
service to create the promise in line 3, and it returns it to the caller in line 18. All that happens immediately – before the HTTP call completes. When the HTTP call completes, the promise is either resolved (to indicate success) or rejected (to indicate failure).
By the way, you may have programmed with $http
and used its success()
and error()
methods; these are $http
specific methods added to Angular promises that are intended to mimic jQuery’s ajax()
call so they’re familiar to jQuery programmers. Whether you use these methods or the more general .then()
and .catch()
, you’re still using promises.
Here’s another code sample that shows how easy and fun it is to chain lots of asynchronous requests together using the then()
method. This snippet is doing a long sequence of REST calls to provision some SharePoint lists. Notice that each then()
function makes a new request and returns its promise; this allows the then()
‘s to be chained. The system is smart enough to catch()
errors from any of the requests in a single call. Although the code is asynchronous, the .then()
methods make it seem almost like it’s synchronous – until you try to debug!
// ensureContent() - Ensure lists and content are initialized var ensureContent = function ensureContent(displayMessage) { spDataService.ensureList('Questions', 'List to hold microsurvey questions') .then( function success(message) { displayMessage(message); return spDataService.ensureColumn('Questions', 'Answers', spDataService.fieldTypes.Text); }) .then( function success2(message) { displayMessage(message); return spDataService.ensureColumnInView('Questions', 'Answers', 'All Items'); }) .then( function success3(message) { displayMessage(message); return spDataService.ensureList('Answers', 'User answers to survey data'); }) .then( function success4(message) { displayMessage(message); return spDataService.ensureColumn('Answers', 'Data', spDataService.fieldTypes.Text); }) .then( function success5(message) { displayMessage(message); return spDataService.ensureColumnInView('Answers', 'Data', 'All Items'); }) .then( function success6(message) { displayMessage(message); }) .catch ( function error(message) { $log.debug('Failure: ' + message); }); };
I hope this helps you to understand JavaScript promises, and that you find them useful in your projects. Thanks to Nick Dikan, Bob Goodearl, and Julie Turner for assistance with this posting – and thank you for reading!
UPDATE: My friend Scot Hillier tells me that he uses this same metaphor when he teaches students about Promises. Scot is one of the best in the business, so I’m thrilled by the prospect that we independently arrived at the same analogy! However it’s possible I heard it from him and subconsciously reused it … who knows. The important thing is that Scot has some great articles on the topic, so let me refer you to them now!