You're Missing the Point of Promises

This post originally appeared as a gist. Since then, the development of
Promises/A+ has made its emphasis on the Promises/A spec seem somewhat outdated.

Contrary to some mistaken statements on the internet, the problems with jQuery’s promises explained here are not fixed
in recent versions; as of 2.1 beta 1 they have all the same problems outlined here, and according to one jQuery core
team member, they will forever remain broken, in the name of backward compatibility.

Promises are a software abstraction that makes working with asynchronous operations much more pleasant. In the most
basic definition, your code will move from continuation-passing style:

to one where your functions return a value, called a promise, which represents the eventual results of that operation.

varpromiseForTweets=getTweetsFor("domenic");

This is powerful since you can now treat these promises as first-class objects, passing them around, aggregating them,
and so on, instead of inserting dummy callbacks that tie together other callbacks in order to do the same.

I’ve talked about how cool I think promises are at length. This essay isn’t about that. Instead, it’s
about a disturbing trend I am seeing in recent JavaScript libraries that have added promise support: they completely
miss the point of promises.

Thenables and CommonJS Promises/A

When someone says “promise” in a JavaScript context, usually they mean—or at least think they
mean—CommonJS Promises/A. This is one of the smallest “specs” I’ve seen. The meat of it is entirely about specifying
the behavior of a single function, then:

A promise is defined as an object that has a function as the value for the property then:

then(fulfilledHandler, errorHandler, progressHandler)

Adds a fulfilledHandler, errorHandler, and progressHandler to be called for completion of a promise. The
fulfilledHandler is called when the promise is fulfilled. The errorHandler is called when a promise fails. The
progressHandler is called for progress events. All arguments are optional and non-function values are ignored. The
progressHandler is not only an optional argument, but progress events are purely optional. Promise implementors are
not required to ever call a progressHandler (the progressHandler may be ignored), this parameter exists so that
implementors may call it if they have progress events to report.

This function should return a new promise that is fulfilled when the given fulfilledHandler or errorHandler
callback is finished. This allows promise operations to be chained together. The value returned from the callback
handler is the fulfillment value for the returned promise. If the callback throws an error, the returned promise will
be moved to failed state.

People mostly understand the first paragraph. It boils down to callback aggregation. You use then to attach
callbacks to a promise, whether for success or for errors (or even progress). When the promise transitions state—which
is out of scope of this very small spec!—your callbacks will be called. This is pretty useful, I guess.

What people don’t seem to notice is the second paragraph. Which is a shame, since it’s the most important one.

What Is the Point of Promises?

The thing is, promises are not about callback aggregation. That’s a simple utility. Promises are about something
much deeper, namely providing a direct correspondence between synchronous functions and asynchronous functions.

What does this mean? Well, there are two very important aspects of synchronous functions:

They return values

They throw exceptions

Both of these are essentially about composition. That is, you can feed the return value of one function straight into
another, and keep doing this indefinitely. More importantly, if at any point that process fails, one function in the
composition chain can throw an exception, which then bypasses all further compositional layers until it comes into the
hands of someone who can handle it with a catch.

Now, in an asynchronous world, you can no longer return values: they simply aren’t ready in time. Similarly, you can’t
throw exceptions, because nobody’s there to catch them. So we descend into the so-called “callback hell,” where
composition of return values involves nested callbacks, and composition of errors involves passing them up the chain
manually, and oh by the way you’d better never throw an exception or else you’ll need to introduce something crazy
like domains.

The point of promises is to give us back functional composition and error bubbling in the async world. They do this
by saying that your functions should return a promise, which can do one of two things:

Become fulfilled by a value

Become rejected with an exception

And, if you have a correctly implemented then function that follows Promises/A, then fulfillment and rejection will
compose just like their synchronous counterparts, with fulfillments flowing up a compositional chain, but being
interrupted at any time by a rejection that is only handled by someone who declares they are ready to handle it.

Note in particular how errors flowed from any step in the process to our catch handler, without explicit by-hand
bubbling code. And with the upcoming ECMAScript 6 revision of JavaScript, plus some party tricks, the code becomes
not only parallel but almost identical.

That Second Paragraph

All of this is essentially enabled by that second paragraph:

This function should return a new promise that is fulfilled when the given fulfilledHandler or errorHandler
callback is finished. This allows promise operations to be chained together. The value returned from the callback
handler is the fulfillment value for the returned promise. If the callback throws an error, the returned promise will
be moved to failed state.

In other words, then is not a mechanism for attaching callbacks to an aggregate collection. It’s a mechanism for
applying a transformation to a promise, and yielding a new promise from that transformation.

This explains the crucial first phrase: “this function should return a new promise.” Libraries like jQuery (before 1.8)
don’t do this: they simply mutate the state of the existing promise. That means if you give a promise out to multiple
consumers, they can interfere with its state. To realize how ridiculous that is, consider the synchronous parallel: if
you gave out a function’s return value to two people, and one of them could somehow change it into a thrown exception!
Indeed, Promises/A points this out explicitly:

Once a promise is fulfilled or failed, the promise’s value MUST not be changed, just as a values in JavaScript,
primitives and object identities, can not change (although objects themselves may always be mutable even if their
identity isn’t).

Now consider the last two sentences. They inform how this new promise is created. In short:

If either handler returns a value, the new promise is fulfilled with that value.

If either handler throws an exception, the new promise is rejected with that exception.

This breaks down into four scenarios, depending on the state of the promise. Here we give their synchronous parallels so
you can see why it’s crucially important to have semantics for all four:

Fulfilled, fulfillment handler throws an exception: getting data, and throwing an exception in response to it

Rejected, rejection handler returns a value: a catch clause got the error and handled it

Rejected, rejection handler throws an exception: a catch clause got the error and re-threw it (or a new one)

Without these transformations being applied, you lose all the power of the synchronous/asynchronous parallel, and your
so-called “promises” become simple callback aggregators. This is the problem with jQuery’s current “promises”: they only
support scenario 1 above, omitting entirely support for scenarios 2–4. This was also the problem with Node.js 0.1’s
EventEmitter-based “promises” (which weren’t even thenable).

Furthermore, note that by catching exceptions and transforming them into rejections, we take care of both intentional
and unintentional exceptions, just like in sync code. That is, if you write aFunctionThatDoesNotExist() in either
handler, your promise becomes rejected and that error will bubble up the chain to the nearest rejection handler just as
if you had written throw new Error("bad data"). Look ma, no domains!

So What?

Maybe you’re breathlessly taken by my inexorable logic and explanatory powers. More likely, you’re asking yourself why
this guy is raging so hard over some poorly-behaved libraries.

Here’s the problem:

A promise is defined as an object that has a function as the value for the property then

As authors of Promises/A-consuming libraries, we would like to assume this statement to be true: that something that
is “thenable” actually behaves as a Promises/A promise, with all the power that entails.

If you can make this assumption, you can write very extensive libraries that are entirely agnostic
to the implementation of the promises they accept! Whether they be from Q, when.js, or even WinJS, you can
use the simple composition rules of the Promises/A spec to build on promise behavior. For example, here’s a generalized
retry function that works with any Promises/A implementation.

Unfortunately, libraries like jQuery break this. This necessitates ugly hacks to detect the presence of objects
masquerading as promises, and who call themselves in their API documentation promises, but aren’t really Promises/A
promises. If the consumers of your API start trying to pass you jQuery promises, you have two choices: fail in
mysterious and hard-to-decipher ways when your compositional techniques fail, or fail up-front and block them from using
your library entirely. This sucks.

Since the release of that test suite, great progress has been made in promise interoperability and understanding. One
library, rsvp.js, was released with the explicit goal of providing these features of Promises/A. Others
followed suit. But the most exciting result was the formation of the Promises/A+ organization,
a loose coalition of implementors who have produced the Promises/A+ specification extending and
clarifying the prose of the original Promises/A spec into something unambiguous and well-tested.

There’s still work to be done, of course. Notably, at current time of writing, the latest jQuery version is 1.9.1, and
its promises implementation is completely broken with regard to the error handling semantics. Hopefully, with the above
explanation to set the stage and the Promises/A+ spec and test suite in place, this problem can be corrected in jQuery
2.0.

In the meantime, here are the libraries that conform to Promises/A+, and that I can thus unreservedly recommend:

Q by Kris Kowal and myself: a full-featured promise library with a large, powerful API surface, adapters for
Node.js, progress support, and preliminary support for long stack traces.

RSVP.js by Yehuda Katz: a very small and lightweight, but still fully compliant, promise library.

when.js by Brian Cavalier: an intermediate library with utilities for managing collections of eventual tasks,
as well as support for both progress and cancellation.

If you are stuck with a crippled “promise” from a source like jQuery, I recommend using one of the above libraries’
assimilation utilities (usually under the name when) to convert to a real promise as soon as possible. For example: