Here's the jsperf: http://jsperf.com/promise-vs-callback
callback case (211 Ops/s):
// async test
var d = deferred;
function getData(callback) {
setTimeout(function() {
callback('data')
}, 0)
}
getData(function(data) {
d.resolve()
})
Promise case(614 ops/s):
// async test
var d = deferred;
function getData() {
return new Promise(function(resolve) {
setTimeout(function() {
resolve('data')
}, 0);
})
}
getData().then(function(data) {
d.resolve()
})
As you see promise are way faster, but they have more code. The question is why this happens.
Here deferred
is to defined by jsperf to show it as the completion of the async test.
As it seems the magic trick lies with in how chrome sets the minimum delay for
setTimeout(fn, 0)
.I searched for it and I found this: https://groups.google.com/a/chromium.org/forum/#!msg/blink-dev/Hn3GxRLXmR0/XP9xcY_gBPQJ
I quote the important part:
In the callback case, setTimeout is called recursively, in a context of another setTimeout , so the minimum timeout is 4ms. In the promise case, setTimeout is actually not called recursively, so the minimum timeout is 0(It wouldn't be actually 0, because other stuff has to run too).
So how do we know setTimeout is called recursively? well we can just conduct an experiment in jsperf or just using benchmark.js:
Which will result in
Uncaught RangeError: Maximum call stack size exceeded.
Which means, once deferred.resolve is called, the test is run again on the same tick/stack. So in the callback case setTimeout is called in it's own calling context and nested in another setTimeout, which will set the minimum timeout to 4ms.But in the promise case,
.then
callback is called after the next tick according to promise spec, and v8 doesn't use setTimeout calling the callback after the next tick. It uses something that must be similar to process.nextTick in nodejs or setImmediate, and not setTimeout. Which resets the setTimeout nesting level to 0 again and makes the setTimeout delay 0ms.First of all, your benchmark is designed wrong. It is only going to measure the minimal
setTimeout
value, not the perf difference between callbacks and promises.The minimal delay is
4ms
so the result cannot be more than 250 operations per second. Somehow callingnew Promise
is removing the minimal 4ms delay.If you wanted to measure promise and callback difference, you need to remove such unnatural bottlenecks. So not only are you measuring at concurrency level of 1, you are waiting 4ms between each call.
JSPErf doesn't make it easy to set concurrency, but here is with concurrency = 1000:
http://jsperf.com/promise-vs-callback/7
As Esailija pointed it's related to weird setTimeout optimizaition in Promise. See also same benchmark made with faster setTimeout alternative: http://jsperf.com/promise-vs-callback/8 it gives more expected results