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/
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.
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/
The promiscuous implementation consists of three major functions:
-
Promise(callback)
serves as a constructor for promises. The callback has two functions as arguments:fulfilled
andrejected
, which can be called to resolve the promise. -
createFinalizedThen
creates a function that can serve as a resolved promise’sthen(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 thefulfilled
orrejected
function passed tothen
. 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/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 thetypeof
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 argumentsfunc
andobj
, a type check simply becomesis(func, then)
, which minifies to something likea(b,c)
. - Who needs booleans to decide between reject or resolve? Just go C-style and use
0
or1
. 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)
anda && (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.