Native Promises vs Bluebird

I’ve been asked, on more than one occasion, about moving to ES6’s native Promises as opposed to using a third party library such as When or Bluebird. In every occasion surprise was expressed when my response was that I intend to continue using Bluebird over native Promises.

Some express surprise over not using native Promises, at times citing concerns that third party Promise libraries might not be as performant as Native Promises but, IMHO, this is a poor decision metric as: a) Promises are mostly used in conjunction with external resources (think DB or API queries) - the bottleneck isn’t the Promise implementation; b) third party Promise implementations are leaner and more performant than Native Promises - at least for now.

My answer always focused on the additional benefits and “sugar” provided by third party libraries when compared to the sparse implementation of Native Promises.

Before I continue with specifics and examples:

A Very Quick Introduction to Promises

In short, Promises “promise” to simplify writing asynchronous code and, more importantly, offer an improved error handling mechanism.

Promises first became mainstream in Javascript when jQuery introduced Deferred Objects in jQuery 1.5 in 2011. Alternative promise libraries started gaining ground soon afterwards but especially after You’re Missing the Point of Promises post which was critical of jQuery’s implementation of Promises via Deferred Objects, specifically in regards to exception handling.

Q initially lead the charge of next generation Promise libraries and found widespread adoption until the arrival of Bluebird which initially rose to prominence due to its benchmarks which showed that Bluebird was faster than other Promise libraries; these benchmarks also highlighted how slow Q was. Bluebird, unlike Q, also provided an API that mirrored Promises/A+ thus providing drop-in support to Native Promises.

Promises are also present in other programming languages: Futures in Dart; concurrent.futures in Python and so on. The Futures and Promises Wikipedia entry details the history of promises/futures.

Much has been written about promises and the advantages, given by their proponents, over callbacks. I won’t go over these here but instead provide the following resources for further reading.

Native ES6 Promises

Native Promises, as defined by the ECMA Script 2015 specifications, provide the following methods:

Advantage of Promise Libraries

Several libraries conform to the Promises/A+ API which include when, bluebird and the now defunct Q. Several other libraries exist that implement basic Promises/A+ spec.

The remainder of this post will concentrate on bluebird and the extended Promises API it provides. When also provides an equally robust API.

Limit Concurrency with .each

When we talk of Javascript’s asynchronous nature, this is specifically in regards to how Javascript interfaces to external sources such as XHR, Database queries, external OS executables and so on. All these operations block in synchronous languages - i.e. querying a database would block execution in say Ruby until the query completes.

Not so in Javascript - these are all asynchronous which can become an issue when synchronous operations are required. A good example would be interfacing to an external API which is subject to rate limiting where it would be desirable to query the API endpoint one request at a time.

Bluebird makes this trivial using each:

1
2
getTwitterAccountIdsFromDb()
.each(account => getAccountInfoFromApiFor(account));

In the above example, getTwitterAccountIdsFromDb will execute sequentially.

Transforming with .map

Bluebird offers several Collections based operators such as any, some, reduce, map amongst others.

map is particularly interesting in that it is also able to limit concurrency. Using the Twitter example from each, we can rewrite the following function:

1
2
let accounts = getTwitterAccountIdsFromDb()
.map(getAccountInfoFromApiFor);

In the above example, accounts will hold an array of objects for each query returned from getAccountInfoFromApiFor. Note that .map, by default, will process in parallel. To limit concurrency:

1
2
let accounts = getTwitterAccountIdsFromDb()
.map(account => getAccountInfoFromApiFor(account), {concurrency: 1});

This will now call getAccountInfoFromApiFor sequentially, once for each account returned from getTwitterAccountIdsFromDb.

Delays with .delay

Keeping with the theme of a rate-limited API endpoint, another useful method is delay, which can be used as follows:

1
2
3
4
5
getTwitterAccountIdsFromDb()
.each(account => {
return transformAccount(account)
.delay(1000 * 60);
});

The above delay will introduce a 60 second delay between each transformAccount call.

timeout is also provided that throws if a promise is not fulfilled within the specified time.

Granular exception handing with .catch

Bluebird provides an extended catch which allows defining catch statements by exception type. This better mirrors exception handling by exception type:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
doSomethingAndReturnAPromise()
.then(function() {
return a.b.c.d();
})
.catch(TypeError, function(e) {
//If it is a TypeError, will end up here because
//it is a type error to reference property of undefined
})
.catch(ReferenceError, function(e) {
//Will end up here if a was never declared at all
})
.catch(function(e) {
//Generic catch-the rest, error wasn't TypeError nor
//ReferenceError
});

Catch-all Mechanism with .finally

Although native promises provide a catch method to capture and bubble exceptions, there isn’t a clean cleanup mechanism that always executes after an exception.

Use cases for this include a loading variable that holds UI state linked to a spinner, for example:

1
2
3
4
5
6
this.loading = true;
getSomeExternalAPIValue()
.then(processValue)
.then(notifyUser)
.catch(displayError)
.finally(() => this.loading = false);

Object Intercepts with .tap

I first came across tap in Ruby, primarily in the following situations:

1
2
3
4
5
def something
result = operation
do_something_with result
result # return
end

Using tap:

1
2
3
4
5
def something
operation.tap do |op|
do_something_with op
end
end

Where tap provides a mechanism for an object to be operated on in a block and then returned - note that the object no longer needs to be returned as Ruby always returns the last object in a block.

Lodash also provides tap, which allows us to apply as similar pattern in Javascript; I often use tap in conjunction with reduce:

1
2
3
4
5
6
7
8
9
10
11
function doSomething() {
return _.reduce(someArray, (result, item) => {
return _.tap(result, r => {
if (item === someCondition){
r.push(item * 2);
} else {
r.push(item / 2);
}
})
}, []);
}

IMHO, this reads better than returning the result at the end of the reduce function.

Bluebird also provides tap as mechanism to intercept the promise success chain without affecting data being passed through the chain.

Instead of:

ang:javascript
1
2
3
4
5
6
doSomething()
.then(...)
.then(item => {
domeSomethingElse();
return item
}).then(...);

tap allows us to simplify this to:

1
2
3
4
doSomething()
.then(...)
.tap(doSomethingElse)
.then(...);

Bluebird provides other useful utility methods such as: call, get, return plus more.

Promise all the Things with Promisification

Last but not least is Bluebird’s wrapper to node type callback function interfaces. Bluebird provides promisify which transforms any function callback with the signature of function(err, data) into a Promise.

Using fs.readfile as an example:

1
2
3
4
5
var readFile = Promise.promisify(require("fs").readFile);
readFile('a_file')
.then(parse)
.then(output)
.catch(displayError);

Finally

Hopefully I’ve shown the added benefits third party Promise libraries provide specifically in terms of convenience and encapsulation of difficult problems such as sequential asynchronous operations, to name a few.

I encourage the reader to visit Bluebird’s API reference. I promise you’ll find it convenient!

The opinions expressed here represent my own and may or may not have any basis in reality or truth. These opinions are completely my own and not those of my friends, colleagues, acquaintances, pets, employers, etc...

The information in this article is provided “AS IS” with no warranties and is unlicensed to the Public Domain. The source code for this website is on GitHub.