Native vs 3rd Party Promise Implementations
TIL that the promise implementation shipped with ES6 is not very performant, and that it is still preferable to use a 3rd party promise library for several reasons.
Note that this post will focus on the 3rd party promise library Bluebird
Why not Native?
I understood that pre-ES6 when there was no native promise implementation, 3rd party libraries were written to fill that gap. Now that native promises are available for use, why use a library to polyfill the behavior?
It appears that there are a few good reasons.
More Performant?
I stumbled across this StackExchange post which asks why native ES6 promises appear slower and less memory efficient than Bluebird promises. It points to a perf test that shows Bluebird promises are around 4x faster and use 4x less memory than native ES6 promises to resolve.
Farther down in that post the Bluebird author responds with some reasoning for the performance differences. The post was written in April of 2015, and after some additional searching I couldn’t tell if the performance data being referenced was still accurate. The performance test data referenced in the README of the bluebird repo was last updated in January of 2015.
I poked around a bit in the bluebird source and the author made it quite easy to re-run benchmarking. Check out the data below. I ran from the following branch which contains a fix for upgrading to the latest version of the “streamline” library (another 3rd party async javascript lib).
Dependency versions
├── async@1.5.2
├── bluebird@3.4.6
├── davy@1.3.0
├── deferred@0.7.5
├── kew@0.7.0
├── lie@3.1.0
├── optimist@0.6.1
├── promise@7.1.1
├── q@1.4.1
├── rsvp@3.3.3
├── streamline@2.0.13
├── vow@0.4.13
└── when@3.7.7
├── rx@2.5.3
├── co@4.6.0
├── baconjs@0.7.89
├── highland@2.10.1
Benchmarking sequential (./bench doxbee)
results for 10000 parallel executions, 1 ms per I/O op
file time(ms) memory(MB)
callbacks-baseline.js 118 30.55
callbacks-suguru03-neo-async-waterfall.js 174 43.49
promises-bluebird-generator.js 213 38.16
promises-bluebird.js 261 48.12
promises-cujojs-when.js 357 61.63
promises-then-promise.js 366 57.88
promises-lvivski-davy.js 445 91.46
promises-tildeio-rsvp.js 505 86.60
callbacks-caolan-async-waterfall.js 617 98.84
promises-dfilatov-vow.js 671 147.82
promises-calvinmetcalf-lie.js 828 138.86
promises-ecmascript6-native.js 1123 183.64
generators-tj-co.js 1170 137.32
promises-obvious-kew.js 1215 214.58
promises-medikoo-deferred.js 1841 164.09
observables-Reactive-Extensions-RxJS.js 2163 224.04
streamline-generators.js 2262 180.45
streamline-callbacks.js 3584 173.93
promises-kriskowal-q.js 11226 835.02
observables-baconjs-bacon.js.js 34624 808.23
observables-pozadi-kefir.js 56509 145.96
observables-caolan-highland.js 128962 457.45
Platform info:
Darwin 16.0.0 x64
Node.JS 6.7.0
V8 5.1.281.83
Intel(R) Core(TM) i5-5257U CPU @ 2.70GHz × 4
Benchmarking parallel (./bench parallel) –p 25
results for 10000 parallel executions, 1 ms per I/O op
file time(ms) memory(MB)
callbacks-baseline.js 268 64.71
promises-bluebird.js 425 99.67
callbacks-suguru03-neo-async-parallel.js 431 82.30
promises-bluebird-generator.js 494 103.07
promises-lvivski-davy.js 740 157.46
promises-cujojs-when.js 935 162.30
callbacks-caolan-async-parallel.js 943 147.88
promises-then-promise.js 1408 312.29
promises-tildeio-rsvp.js 1613 350.70
promises-calvinmetcalf-lie.js 1745 369.84
promises-ecmascript6-native.js 2379 448.05
promises-dfilatov-vow.js 2850 528.65
promises-medikoo-deferred.js 4588 413.72
promises-obvious-kew.js 5024 634.53
streamline-generators.js 23243 1201.41
streamline-callbacks.js 43371 1312.59
Platform info:
Darwin 16.0.0 x64
Node.JS 6.7.0
V8 5.1.281.83
Intel(R) Core(TM) i5-5257U CPU @ 2.70GHz × 4
Looks like Bluebird still comes out on top for both speed and memory usage when compared to other promise implementations.
When compared to ES6, it is still 4.3x faster and 3.8x more memory efficient, at least under these test conditions.
Impressive!
Killer Feature: Promisify
Performance aside, Bluebird offers an awesome feature called promisification, which allows you to wrap another library and have it return promises instead! Examples from the docs:
var fs = require("fs");
Promise.promisifyAll(fs);
// Now you can use fs as if it was designed to use bluebird promises from the beginning
fs.readFileAsync("file.js", "utf8").then(...)
You can do the same thing with database clients, or other async libraries that are not natively promise-based (and instead accept callbacks).
Also amazing!
Can I use?
According to can I use native promises are ready for use in most current version browsers today, but not all. If you’re looking to support legacy browsers (or IE), using a 3rd party promise library is a nice alternative to relying on an implementation in the browser.
Conclusion
It seems like of your 3rd party async-handling Javascript libraries, bluebird is still the best bet. It offers promisification and is more performant than native ES6 promises. I’ll be using it in my next project for sure.
Comments