208

In this benchmark, the suite takes 4 times longer to complete with ES6 promises compared to Bluebird promises, and uses 3.6 times as much memory.

How can a JavaScript library be so much faster and lighter than v8's native implementation written in C? Bluebird promises have exactly the same API as native ES6 promises (plus a bunch of extra utility methods).

Is the native implementation just badly written, or is there some other aspect to this that I'm missing?

callum
  • 10,377
  • 9
  • 30
  • 33
  • Keep in mind that modern JavaScript implementations are heavily optimized, and may even [run natively](http://stackoverflow.com/q/7807235/439793) using [JIT](http://en.wikipedia.org/wiki/Just-in-time_compilation). –  Apr 10 '15 at 20:28
  • 1
    According to [This Benchmark](https://jsperf.com/promises-native-vs-bluebird-vs-promisemespeed/1), BlueBirdJS is actually slower than Native Promises. But, PromiseMeSpeedJS actually outpaces both of them. One of the many things that PromiseMeSpeedJS proves through this is that a major performance culprit for promises is the abusive overuse of the `new` operator because PromiseMeSpeedJS does not use `new`. – Jack G Feb 17 '18 at 16:34
  • 1
    @JackGiffin Chrome 67: PromiseMeSpeedJS is 46% slower and Bluebird is 61% slower. – FINDarkside Jul 04 '18 at 11:18
  • That benchmark link is broken now. does someone have any other references? – Ashu Sahu Dec 15 '22 at 08:30

1 Answers1

286

Bluebird author here.

V8 promises implementation is written in JavaScript not C. All JavaScript (including V8's own) is compiled to native code. Additionally user written JavaScript is optimized, if possible (and worth it), before compiled to native code. Promises implementation is something that would not benefit much or at all from being written in C, in fact it would only make it slower because all you are doing is manipulating JavaScript objects and communication.

The V8 implementation simply isn't as optimized as bluebird, it for instances allocates arrays for promises' handlers. This takes a lot of memory when each promise also has to allocate a couple of arrays (The benchmark creates overall 80k promises so that's 160k unused arrays allocated). In reality 99.99% of use cases never branch a promise more than once so optimizing for this common case gains huge memory usage improvements.

Even if V8 implemented the same optimizations as bluebird, it would still be hindered by specification. The benchmark has to use new Promise (an anti-pattern in bluebird) as there is no other way to create a root promise in ES6. new Promise is an extremely slow way of creating a promise, first the executor function allocates a closure, secondly it is passed 2 separate closures as arguments. That's 3 closures allocated per promise but a closure is already a more expensive object than an optimized promise.

Bluebird can use promisify which enables lots of optimizations and is a much more convenient way of consuming callback APIs and it enables conversion of whole modules into promise based modules in one line (promisifyAll(require('redis'));).

Esailija
  • 5,364
  • 1
  • 19
  • 16
  • Good answer, thank you. I didn't realise `new Promise` was so slow. Your [anti-patterns page](https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns) doesn't mention that btw. – callum Apr 14 '15 at 08:28
  • 2
    Because it's not an anti-pattern "proper", but maybe it should be added because it's useful to know – Benjamin Gruenbaum Apr 14 '15 at 08:51
  • 11
    "still be hindered by specification" - Not sure what that means. Are you saying that ES6 is following a spec that is inherently slow, and if that's the case, does that mean bluebird is not following the same spec (and if that's the case, is it following a different one, and which one)? And is there any reason why ES6 couldn't have a better way of creating a root Promise besides `new Promise` or improve the instantiation to make it less expensive (like not creating 3 closures per instance)? – Anthony Jun 03 '15 at 12:40
  • Also, what about the arrays-per-promise bottleneck? Is the issue that ES6 can't assume that a promise probably won't branch more than once? Does bb run into any problems when a promise does branch more than once (the .01% that it does)? I'm just curious if bluebird is faster than native ES6 because ES6 has some built in design constraints that prevent it from implementing the optimizations that bb does, or if bb is sacrificing full compliance (to whatever ES6 is complying to) in the interest of significant performance boosts. If its all JS, then why can't ES6/V8 use bb as the native? – Anthony Jun 03 '15 at 12:47
  • 1
    @Anthony There is no spec for `promisify` or `promisifyAll` so implementers of ES6 don't implement it. `new Promise` is very slow in bluebird as well, it's just impossible to make it fast. As for array-per-promise, it's an implementer's optimization, it has nothing to do with the spec. – Esailija Jun 03 '15 at 15:38
  • 12
    That doesn't sound good at all (for JS). I really don't want to use a Promise library when there's an internal implementation. This is a more than unfortunate situation for everyone if this is all true. But I already have trouble seeing the Promise-hype anyway, I've written 100,000 LoC JS apps and I still don't see any real need for this, it's a very minor improvement if at all _to me_, mostly in error handling, no improvement in callback handling (I've never been in "callback hell" with my coding style). – Mörre Jun 15 '15 at 08:44
  • 1
    You have to use a library for any form of callback aggregation or composition anyway (unless you want to do some *really* nasty hacks or roll your own every project you do) The only difference between bluebird and async (or similar) in that regard is that, in my opinion, bluebird reads easier. Obviously, this is subjective, so you should choose which suits you better. @MörreNoseshine – Dan Jul 15 '15 at 08:14
  • @Esailija perhaps you could contribute to the development of ES6 and ES7 since you have a better idea – geoyws Oct 05 '15 at 04:25
  • 20
    In ES6, can't you use `Promise.resolve()` to create a "root promise"? – zetlen Nov 13 '15 at 16:02
  • 3
    "Native" is a misnomer; it's not C++ code. And if it were, it would likely be a lot slower yet due to having to constantly cross the C++/JavaScript boundary. The V8 promise implementation just happens to be JavaScript code that's bundled with the JavaScript interpreter. Saying you don't want to use Bluebird because a Promise implementation is built-in, is like saying your don't want to install an IDE because Windows already comes with Notepad. And the ES6 spec is too simplistic to be useful anyway; you'll eventually end up installing bluebird. – gx0r Jan 07 '16 at 20:14
  • 1
    @zetlen yes but it's already resolved and cannot be controlled – Esailija Jan 20 '16 at 11:58
  • 4
    @MörreNoseshine "I really don't want to use a Promise library when there's an internal implementation." I think you're thinking about this wrong. ES6's `Promise` global is not special or definitive. The promise standard is called Promises/A+, it was written long before ES6, and it defines promises as a duck-type that different codebases can agree on for passing around control flow. – callum Jan 21 '16 at 12:37
  • 10
    @MörreNoseshine (continued) Years later, the ES6 authors came along and said "hey, let's specify that JS engines must provide a generic Promises/A+ conforming utility out-of-the-box, so people always have a basic promise tool to hand". This is a nice convenience (not having to import a library just to do a quick `Promise.resolve()` or whatever), but it's a very basic implementation, and its existence should not put you off using more serious promise-related tools like bluebird! – callum Jan 21 '16 at 12:39
  • 2
    @callum ES 2015 already implements promises. I don't see you providing anything new to my original objection above, so I can can only refer to my previous comment. It's just my opinion, I don't see a need to have a discussion about this that seems to aim at changing ones mind. There is nothing to argue between us. I do what I do and you do whatever you want. – Mörre Jan 21 '16 at 13:51
  • 1
    @MörreNoseshine sure, I only mean to have a friendly discussion, if you're interested :) You said it's an "unfortunate situation for everyone if this is all true", but I don't think it's so unfortunate if you look at it this way: Bluebird is just a library that follows the Promises/A+ standard, while V8's `Promise` object is an implementation the ES6 Promise spec. The latter spec defines a very particular, rigorous, play-it-safe API for making promises. So it's fair enough that a tool that *doesn't* implement that spec (e.g. Bluebird) could easily outperform V8's `Promise`. – callum Jan 21 '16 at 14:40
  • 11
    @MörreNoseshine 100k LOC Javascript app that probably never had any async functionality. Good luck writing a 100k LoC JS game with a mysql / redis library without bluebird. – NiCk Newman Mar 07 '16 at 15:26
  • 4
    @NiCkNewman: Promises are just a syntax sugar for writing callbacks. In fact, it is still callbacks only inside .then() rather than in the main function. You can have callback hell with promises. But then people will tell you that you're using promises wrong - that you should chain them instead of nest them. Same thing can (and has) been said for regular callbacks - that you should not nest them to avoid callback hell. It's very-very easy to write 100k loc js game with mysql/redis without bluebird or any other promise library. – slebetman Apr 19 '16 at 04:40
  • @Esailija @callum @Benjamin @Anthony If `new Promise()` is slow, then what's the "proper" way to create promises in Bluebird? E.g., in Q you can use `Q.promise()`... – Septagram Aug 13 '16 at 12:28
  • @Septagram See my answer below for a comparison of new Promise with bluebird and native. Bluebird is an order of magnitude faster. – rsp Sep 25 '16 at 16:36
  • @Esailija What do you mean that it's impossible to make it faster? Have you ever benchmarked new Promise with bluebird and native? See the tests in [my answer](http://programmers.stackexchange.com/questions/278778/why-are-native-es6-promises-slower-and-more-memory-intensive-than-bluebird/331995#331995). It seems certainly possible to make it faster because Bluebird is much faster. – rsp Sep 25 '16 at 16:39
  • @rsp For some weird reason, I don't see your answer :( Also, isn't `new Promise()` the [Bluebird way](http://bluebirdjs.com/docs/api/new-promise.html)? – Septagram Sep 25 '16 at 21:29
  • @Septagram I'm sorry to hear that. I see that my answer was: "deleted by gnat, ChrisF♦ 12 hours ago." You can see my other answer [here](http://programmers.stackexchange.com/questions/331991/are-native-promises-still-slower-in-node-vs-libraries-such-as-bluebird/331992#331992) for examples on how I use Bluebird and how much it is faster than native Promise. – rsp Sep 26 '16 at 06:05
  • 1
    @Esailija does your expert opinion change when spec promises are [implemented as c++](https://bugs.chromium.org/p/v8/issues/detail?id=5343)? – Redsandro Jan 13 '17 at 15:07
  • @slebetman: There is more to promises than that. The strength of promises is in the implicit chaining up the call stack. This is ignoring stuff like Promise.all or Promise.any. – Tim Jan 23 '17 at 02:16
  • @HasFiveVowels: "chaining up the call stack" - what does that even mean? Promises are just chainable objects with callback receivers (the `.then()`). It's just a design pattern. Nothing special – slebetman Jan 23 '17 at 03:14
  • @slebetman - You could say that C is just "syntactic sugar" over assembly, in that C doesn't enable anything that assembly is unable to do. Reminds me of this article: [If Haskell is so great, why hasn't it taken over the world? And the curious case of Go.](https://pchiusano.github.io/2017-01-20/why-not-haskell.html) (by Paul Chiusano) – JDB Jan 23 '17 at 19:16
  • 1
    @JDB: Not quite at the same level. While C IS just a layer over assembly you can't write C code in assembly. You first need a compiler to convert the C to assembly. Promises on the other hand is pure javascript implemented using callbacks. It needs no new features in the language itself. A better analogy is jQuery and javascript. Yes, when jQuery first appeared people had the same reaction some people now have with Promises. But now people know that it is just a pure js library... – slebetman Jan 24 '17 at 03:08
  • 1
    @JDB ... The result of which is now people are more willing to completely abandon jQuery and use other libraries like underscore or Angular re React. Same with promises. It's not the best in all cases. In some cases plain callbacks + async.js is superior. Particularly if the callbacks need looping or recursion. Promises don't handle them as well as plain callbacks. – slebetman Jan 24 '17 at 03:10
  • @slebetman A bit late here. What I meant is that the real power of promises comes from when you have several layers of promise-based code calling each other. You don't have to program in error handling for each layer. If `a()` calls `b()` calls `c()` and an error happens in `c()`, you can catch it in `a()` without having to write `if(error){ return callback(error, null);}` in `b()`. Furthermore, you're not having to pass around a bunch of callbacks in your params. – Tim Jan 30 '17 at 18:34
  • 2
    Are native promises still slower than bluebird in late 2017? – B T Oct 27 '17 at 05:39
  • 2
    @BT a [benchmark](http://bluebirdjs.com/docs/benchmarks.html) with Node.JS v7.7.1 still shows that bluebird is substantially faster than native Promise. – mrkvon Dec 09 '17 at 00:24
  • 1
    From Bluebird github page: "Promises in Node.js 10 are significantly faster than before. Bluebird still includes a lot of features like cancellation, iteration methods and warnings that native promises don't. If you are using Bluebird for performance rather than for those - please consider giving native promises a shot and running the benchmarks yourself." – Naor Oct 24 '18 at 08:19
  • 1
    I can't help but feel if you are optimizing your Promises for performance, you are treading into premature optimization territory. – rich remer Nov 13 '18 at 18:40
  • @Esailija I don't suppose that ES6 `Promise.resolve().then(() => { /* what I actually want to do */ })` is faster than `new Promise(...)`? Seems to be what @zetlen was hinting at – Andy Feb 14 '19 at 20:24
  • @Andy No, @Esailija was right and I wasn't. The only way to turn a callback-style async function into a native Promise is with `new Promise(handler)`. Inside a `.then`, you still have to return a value; so, to transform a callback-style async function into a promise-returning function, you need to be able to pass a reference to a `resolve` callback. – zetlen Mar 19 '20 at 15:08