[Profile picture of Ruben Verborgh]

Ruben Verborgh

Promiscuous promises

Here's how a tiny spec-compatible Promise library in JavaScript works.

Promises allow to asynchronously return a value from a synchronous function. If the return value is not known when the function exists, you can return a promise that will be fulfilled with that value later on. This comes in handy for JavaScript, which is often used for Web programming and thus has to rely on asynchronous return values: downloads, API calls, read/​write operations, … In those cases, promises can make your code easier and thus better.
This post briefly explains promises and then zooms in on the methods I used to create promiscuous, a small Promise implementation for JavaScript.

Promises solve the problem of the callback pyramid or callback hell that occurs when programming asynchronously. Let’s start with a simple synchronous example:

var x = sum(1, 5);
var y = quotient(x, 3);
console.log('The result is', y);

The above code calculates the result of (1 + 5) / 3. Full code here.

Now imagine that, for some reason, sum and quotient take some time to complete. We don’t want to wait actively for completion, so we execute them asynchronously.
The traditional JavaScript way is to solve this with callbacks:

sumC(1, 5, function (x) {
  quotientC(x, 3, function (y) {
    console.log('The result is', y);
  });
});

Note how we don’t use return values from the sum and quotient functions;
instead, they send the result of their calculation to a callback function.
It’s not hard to imagine how this leads to long and complicated code.

“He is poor indeed that can promise nothing.”— Thomas Fuller   ©little.lions

The idea of promises is to return a special return value: a promise. Actions can be chained on this promise, which are then executed when the promise is fulfilled.

sumP(1, 5)
  .then(function (x) { return quotientP(x, 3); })
  .then(function (y) { console.log('The result is', y); });

Note how the above code captures the intention of the programmer better; and this benefit increases with real-world code.

There’s much more to promises; for instance, they also provide nice error handling. You can read all about them on HTML5 Rocks. This remainder of this blog post discusses the internals of my realy tiny promise library called promiscuous.

Implementing a tiny promise library

Promises will be part of the upcoming JavaScript version ES6. Chrome and Firefox already started implementing Promise support, but until this is widely supported, you will need a library to benefit from them.

Since I believe promises are a very handy programming tool, I’ve committed myself to create a tiny library that brings promise support. The Promises/​A+ spec explains the rules a well-behaved Promise implementation should obey. My goal was to create the smallest possible implementation of these rules, while still delivering good performance. The current code passes the spec in under 1.000 characters minified code, and even under 600 characters gzipped.

The promiscuous implementation consists of three major functions:

  • Promise(callback) serves as a constructor for promises. The callback has two functions as arguments: fulfilled and rejected, which can be called to resolve the promise.

  • createFinalizedThen creates a function that can serve as a resolved promise’s then(fulfilled, rejected). Depending on the promise’s final state, then will either call the first or the second callback.

  • finalize fulfills or rejects a promise with a value, transformed by the fulfilled or rejected function passed to then. This enables chaining.

Inside these functions are more helper functions; a rather solid understanding of higher-order functions and closures is recommended before diving into the code ;-)

Implicit states

The major design decision is to keep state implicitly. That is, given a promise, there’s no need to determine whether it has been fulfilled/​rejected; rather, you just use the then method to handle this. Most other promises libraries (such as the RSVP.js or Q implementations) explicitly store the state of the promise. Instead, promiscuous implements a promise to implicitly capture the possible states:

{
  then: function (fulfilled, rejected) {
    return handler(fulfilled, rejected);
  }
}

The handler is captured in a closure and initially has queuing functionality. When the promise state changes, handler becomes a simple function that calls either the first or the second argument, depending on whether the promise is fulfilled or rejected. The state of the promise thus resides entirely in the closure. As an added benefit, none of the variables associated with the pending states (such as the queue of callbacks) have to be remembered when the promise becomes settled.

Interestingly, the initial pendingHandler of promiscuous serves a double purpose. The promise fate can only switch once from resolved to unresolved. Hence, calling the fulfill or reject callbacks of the Promise constructor after this does not have any effect. Since the timing coincides with the switch of handler, this function can additionally perform the task of fulfilling or rejecting the promise:

function fulfill(value) { handler(is, 1,  value); }
function reject(reason) { handler(is, 0, reason); }

The first argument serves as a sentinel. If it equals the internal is function, the handler call is treated as “fulfill” or “reject”, depending on argument two. By assigning the final value to handler when the fate changes, the then function gets updated and the resolvers become disabled at the same time.

Tweaks for minimal size

In addition to this architectural decision, several fun tweaks were used to keep the code size at a bare minimum:

  • No var statements have been used. At all. Instead, variables are created by passing extra arguments to functions. Since they are directly assigned to, no confusion with passed argument values is possible.
  • Variables are reused to avoid extra variable names. Admittedly, this makes the code harder to follow, but code size was deemed more important here.
  • Type checks are made by a dedicated is function, since they occur often and the typeof keyword cannot be minimized. Furthermore, we only need to check the first character of the type string, as 'function' and 'object' are the only ones starting with 'f' and 'o'.
  • We can still save more on type checks! By only encoding 'f' and 'o' once and passing them as arguments func and obj, a type check simply becomes is(func, then), which minifies to something like a(b,c).
  • Who needs booleans to decide between reject or resolve? Just go C-style and use 0 or 1. Plus, they make array indexes short, too. (So do single letters.)
  • And finally, shorter operators save some characters. There’s no difference between the boolean interpretation of a && (b | c) and a && (b || c). But it’s one character shorter. The same with == instead of ===, if used wisely.

While I don’t recommend any of this trickery as a general strategy, it really served well to make promiscuous really small. Have a look at the minified code. It’s extra cool, considering this implements a full-page spec and passes an 872-item test suite.

Thus, if you need promises with minimal impact, promiscuous is the ideal library.

Ruben Verborgh

Enjoyed this blog post? Subscribe to the feed for updates!

Comment on this post