Register for this year’s #ChromeDevSummit happening on Nov. 11-12 in San Francisco to learn about the latest features and tools coming to the Web. Request an invite on the Chrome Dev Summit 2019 website

People are cheering around you, but you're not sure what all the fuss is
about. Maybe you're not even sure what a "promise" is. You'd shrug, but the
weight of glittery paper is weighing down on your shoulders. If so, don't
worry about it, it took me ages to work out why I should care about this
stuff. You probably want to begin at the beginning.

You punch the air! About time right? You've used these Promise things before
but it bothers you that all implementations have a slightly different API.
What's the API for the official JavaScript version? You probably want to begin
with the terminology.

You knew about this already and you scoff at those who are jumping up and
down like it's news to them. Take a moment to bask in your own superiority,
then head straight to the API reference.

What's all the fuss about?

JavaScript is single threaded, meaning that two bits of script cannot run at
the same time; they have to run one after another. In browsers, JavaScript
shares a thread with a load of other stuff that differs from browser to
browser. But typically JavaScript is in the same queue as painting, updating
styles, and handling user actions (such as highlighting text and interacting
with form controls). Activity in one of these things delays the others.

As a human being, you're multithreaded. You can type with multiple fingers,
you can drive and hold a conversation at the same time. The only blocking
function we have to deal with is sneezing, where all current activity must
be suspended for the duration of the sneeze. That's pretty annoying,
especially when you're driving and trying to hold a conversation. You don't
want to write code that's sneezy.

You've probably used events and callbacks to get around this. Here are events:

This doesn't catch images that error'd before we got a chance to listen for
them; unfortunately the DOM doesn't give us a way to do that. Also, this is
loading one image, things get even more complex if we want to know when a set
of images have loaded.

Events aren't always the best way

Events are great for things that can happen multiple times on the same
object—keyup, touchstart etc. With those events you don't really care
about what happened before you attached the listener. But when it comes to
async success/failure, ideally you want something like this:

A promise can only succeed or fail once. It cannot succeed or fail twice,
neither can it switch from success to failure or vice versa.

If a promise has succeeded or failed and you later add a success/failure
callback, the correct callback will be called, even though the event took
place earlier.

This is extremely useful for async success/failure, because you're less
interested in the exact time something became available, and more interested
in reacting to the outcome.

Promise terminology

Domenic Denicola proof read the first draft
of this article and graded me "F" for terminology. He put me in detention,
forced me to copy out
States and Fates
100 times, and wrote a worried letter to my parents. Despite that, I still
get a lot of the terminology mixed up, but here are the basics:

A promise can be:

fulfilled - The action relating to the promise succeeded

rejected - The action relating to the promise failed

pending - Hasn't fulfilled or rejected yet

settled - Has fulfilled or rejected

The spec
also uses the term thenable to describe an object that is promise-like,
in that it has a then method. This term reminds me of ex-England Football
Manager Terry Venables so
I'll be using it as little as possible.

Promises arrive in JavaScript!

Promises have been around for a while in the form of libraries, such as:

The above and JavaScript promises share a common, standardized behaviour
called Promises/A+. If
you're a jQuery user, they have something similar called
Deferreds. However,
Deferreds aren't Promise/A+ compliant, which makes them
subtly different and less useful,
so beware. jQuery also has
a Promise type, but this is just a
subset of Deferred and has the same issues.

Although promise implementations follow a standardized behaviour, their
overall APIs differ. JavaScript promises are similar in API to RSVP.js.
Here's how you create a promise:

The promise constructor takes one argument, a callback with two parameters,
resolve and reject. Do something within the callback, perhaps async, then call
resolve if everything worked, otherwise call reject.

Like throw in plain old JavaScript, it's customary, but not required, to
reject with an Error object. The benefit of Error objects is they capture a
stack trace, making debugging tools more helpful.

then() takes two arguments, a callback for a success case, and another
for the failure case. Both are optional, so you can add a callback for the
success or failure case only.

JavaScript promises started out in the DOM as "Futures", renamed to "Promises",
and finally moved into JavaScript. Having them in JavaScript rather than the
DOM is great because they'll be available in non-browser JS contexts such as
Node.js (whether they make use of them in their core APIs is another question).

Browser support & polyfill

To bring browsers that lack a complete promises implementation up to spec
compliance, or add promises to other browsers and Node.js, check out
the polyfill
(2k gzipped).

Compatibility with other libraries

The JavaScript promises API will treat anything with a then() method as
promise-like (or thenable in promise-speak sigh), so if you use a library
that returns a Q promise, that's fine, it'll play nice with the new
JavaScript promises.

Although, as I mentioned, jQuery's Deferreds are a bit … unhelpful.
Thankfully you can cast them to standard promises, which is worth doing
as soon as possible:

var jsPromise = Promise.resolve($.ajax('/whatever.json'))

Here, jQuery's $.ajax returns a Deferred. Since it has a then() method,
Promise.resolve() can turn it into a JavaScript promise. However,
sometimes deferreds pass multiple arguments to their callbacks, for example:

Thankfully this is usually what you want, or at least gives you access to
what you want. Also, be aware that jQuery doesn't follow the convention of
passing Error objects into rejections.

Complex async code made easier

Right, let's code some things. Say we want to:

Start a spinner to indicate loading

Fetch some JSON for a story, which gives us the title, and urls for each chapter

Add title to the page

Fetch each chapter

Add the story to the page

Stop the spinner

… but also tell the user if something went wrong along the way. We'll want
to stop the spinner at that point too, else it'll keep on spinning, get
dizzy, and crash into some other UI.

Of course, you wouldn't use JavaScript to deliver a story,
serving as HTML is faster,
but this pattern is pretty common when dealing with APIs: Multiple data
fetches, then do something when it's all done.

To start with, let's deal with fetching data from the network:

Promisifying XMLHttpRequest

Old APIs will be updated to use promises, if it's possible in a backwards
compatible way. XMLHttpRequest is a prime candidate, but in the mean time
let's write a simple function to make a GET request:

getJSON() still returns a promise, one that fetches a url then parses
the response as JSON.

Queuing asynchronous actions

You can also chain thens to run async actions in sequence.

When you return something from a then() callback, it's a bit magic.
If you return a value, the next then() is called with that value. However,
if you return something promise-like, the next then() waits on it, and is
only called when that promise settles (succeeds/fails). For example:

The difference is subtle, but extremely useful. Promise rejections skip
forward to the next then() with a rejection callback (or catch(), since
it's equivalent). With then(func1, func2), func1 or func2 will be
called, never both. But with then(func1).catch(func2), both will be
called if func1 rejects, as they're separate steps in the chain. Take
the following:

If fetching story.chapterUrls[0] fails (e.g., http 500 or user is offline),
it'll skip all following success callbacks, which includes the one in
getJSON() which tries to parse the response as JSON, and also skips the
callback that adds chapter1.html to the page. Instead it moves onto the catch
callback. As a result, "Failed to show chapter" will be added to the page if
any of the previous actions failed.

Like JavaScript's try/catch, the error is caught and subsequent code
continues, so the spinner is always hidden, which is what we want. The
above becomes a non-blocking async version of:

This is the first time we've seen Promise.resolve(), which creates a
promise that resolves to whatever value you give it. If you pass it an
instance of Promise it'll simply return it (note: this is a
change to the spec that some implementations don't yet follow). If you
pass it something promise-like (has a then() method), it creates a
genuine Promise that fulfills/rejects in the same way. If you pass
in any other value, e.g., Promise.resolve('Hello'), it creates a
promise that fulfills with that value. If you call it with no value,
as above, it fulfills with "undefined".

There's also Promise.reject(val), which creates a promise that rejects with
the value you give it (or undefined).

This is doing the same as the previous example, but doesn't need the separate
"sequence" variable. Our reduce callback is called for each item in the array.
"sequence" is Promise.resolve() the first time around, but for the rest of the
calls "sequence" is whatever we returned from the previous call. array.reduce
is really useful for boiling an array down to a single value, which in this case
is a promise.

And there we have it (see
code),
a fully async version of the sync version. But we can do better. At the moment
our page is downloading like this:

Browsers are pretty good at downloading multiple things at once, so we're losing
performance by downloading chapters one after the other. What we want to do is
download them all at the same time, then process them when they've all arrived.
Thankfully there's an API for this:

Promise.all(arrayOfPromises).then(function(arrayOfResults) {
//...
})

Promise.all takes an array of promises and creates a promise that fulfills
when all of them successfully complete. You get an array of results (whatever
the promises fulfilled to) in the same order as the promises you passed in.

Depending on connection, this can be seconds faster than loading one-by-one (see
code),
and it's less code than our first try. The chapters can download in whatever
order, but they appear on screen in the right order.

However, we can still improve perceived performance. When chapter one arrives we
should add it to the page. This lets the user start reading before the rest of
the chapters have arrived. When chapter three arrives, we wouldn't add it to the
page because the user may not realize chapter two is missing. When chapter two
arrives, we can add chapters two and three, etc etc.

To do this, we fetch JSON for all our chapters at the same time, then create a
sequence to add them to the document:

And there we go (see
code),
the best of both! It takes the same amount of time to deliver all the content,
but the user gets the first bit of content sooner.

In this trivial example, all of the chapters arrive around the same time, but
the benefit of displaying one at a time will be exaggerated with more, larger
chapters.

Doing the above with Node.js-style callbacks or
events is around
double the code, but more importantly isn't as easy to follow. However, this
isn't the end of the story for promises, when combined with other ES6 features
they get even easier.

Bonus round: promises and generators

This next bit involves a whole bunch of new ES6 features, but it's not something
you need to understand to use promises in your code today. Treat it like a movie
trailer for some upcoming blockbuster features.

ES6 also gives us
generators,
which allow functions to exit at a particular point, like "return", but
later resume from the same point and state, for example:

But what does this mean for promises? Well, you can use this return/resume
behaviour to write async code that looks like (and is as easy to follow as)
synchronous code. Don't worry too much about understanding it line-for-line, but
here's a helper function that lets us use yield to wait for promises to
settle:

This works exactly as before but is so much easier to read. This works in
Chrome and Opera today (see
code),
and works in Microsoft Edge by going to about:flags and turning on
the Enable experimental JavaScript features setting. This will be
enabled by default in an upcoming version.

This throws together a lot of new ES6 stuff: promises, generators, let, for-of.
When we yield a promise, the spawn helper waits for the promise to resolve and
returns the final value. If the promise rejects, spawn causes our yield
statement to throw an exception, which we can catch with normal JavaScript
try/catch. Amazingly simple async coding!

This pattern is so useful, it's coming to ES7 in the form of
async functions. It's
pretty much the same as above, but no need for a spawn method.

Promise API reference

All methods work in Chrome, Opera, Firefox, Microsoft Edge, and Safari
unless otherwise noted. The
polyfill provides
the below for all browsers.

Static Methods

Method summaries

Promise.resolve(promise);

Returns promise (only if promise.constructor == Promise)

Promise.resolve(thenable);

Make a new promise from the thenable. A thenable is promise-like in as
far as it has a `then()` method.

Promise.resolve(obj);

Make a promise that fulfills to obj. in this situation.

Promise.reject(obj);

Make a promise that rejects to obj. For consistency and
debugging (e.g. stack traces), obj should be an
instanceof Error.

Promise.all(array);

Make a promise that fulfills when every item in the array fulfills, and
rejects if (and when) any item rejects. Each array item is passed to
Promise.resolve, so the array can be a mixture of
promise-like objects and other objects. The fulfillment value is
an array (in order) of fulfillment values. The rejection value is
the first rejection value.

Promise.race(array);

Make a Promise that fulfills as soon as any item fulfills, or rejects as
soon as any item rejects, whichever happens first.

Note: I'm unconvinced of Promise.race's usefulness; I'd rather have an
opposite of Promise.all that only rejects if all items reject.

Constructor

Constructor

new Promise(function(resolve, reject) {});

resolve(thenable)
Your promise will be fulfilled/rejected with the outcome of
thenable

resolve(obj)
Your promise is fulfilled with obj

reject(obj)
Your promise is rejected with obj. For consistency and
debugging (e.g., stack traces), obj should be an instanceof
Error. Any errors thrown in the constructor callback will be
implicitly passed to reject().

Instance Methods

Instance Methods

promise.then(onFulfilled, onRejected)

onFulfilled is called when/if "promise" resolves.
onRejected is called when/if "promise" rejects. Both are
optional, if either/both are omitted the next
onFulfilled/onRejected in the chain is called.
Both callbacks have a single parameter, the fulfillment value or
rejection reason. then() returns a new promise equivalent to
the value you return from onFulfilled/onRejected
after being passed through Promise.resolve. If an error is
thrown in the callback, the returned promise rejects with that error.

promise.catch(onRejected)

Sugar for promise.then(undefined, onRejected)

Feedback

Was this page helpful?

Yes

What was the best thing about this page?

It helped me complete my goal(s)

Thank you for the feedback. If you have specific ideas on how to improve this page, please
create an issue.

It had the information I needed

Thank you for the feedback. If you have specific ideas on how to improve this page, please
create an issue.

It had accurate information

Thank you for the feedback. If you have specific ideas on how to improve this page, please
create an issue.

It was easy to read

Thank you for the feedback. If you have specific ideas on how to improve this page, please
create an issue.

Something else

Thank you for the feedback. If you have specific ideas on how to improve this page, please
create an issue.

No

What was the worst thing about this page?

It didn't help me complete my goal(s)

Thank you for the feedback. If you have specific ideas on how to improve this page, please
create an issue.

It was missing information I needed

Thank you for the feedback. If you have specific ideas on how to improve this page, please
create an issue.

It had inaccurate information

Thank you for the feedback. If you have specific ideas on how to improve this page, please
create an issue.

It was hard to read

Thank you for the feedback. If you have specific ideas on how to improve this page, please
create an issue.

Something else

Thank you for the feedback. If you have specific ideas on how to improve this page, please
create an issue.