为什么在这种情况下,本机承诺似乎比chrome中的回调更快?

Far*_*hat 3 javascript performance google-chrome v8 promise

这是jsperf:http://jsperf.com/promise-vs-callback

回调案例(211 Ops/s):

// async test
var d = deferred;

function getData(callback) {
  setTimeout(function() {
    callback('data')
  }, 0)
}

getData(function(data) {
  d.resolve()
})
Run Code Online (Sandbox Code Playgroud)

承诺案例(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()
})
Run Code Online (Sandbox Code Playgroud)

如你所见,承诺更快,但他们有更多的代码.问题是为什么会发生这种情况.

deferred是由jsperf定义的,以显示它作为异步测试的完成.

Far*_*hat 7

看起来神奇的技巧在于铬如何设置最小延迟setTimeout(fn, 0).

我搜索了它,我发现了这个:https://groups.google.com/a/chromium.org/forum/#!msg/blink-dev/Hn3GxRLXmR0/XP9xcY_gBPQJ

我引用了重要的部分:

定时器钳位的工作方式是每个任务都有一个相关的定时器嵌套级别.如果任务源自setTimeout()或setInterval()调用,则嵌套级别大于调用setTimeout()的任务的嵌套级别或该setInterval()的最近迭代的任务,否则为零.只有嵌套级别为4或更高时,4ms钳位才适用.在事件处理程序,动画回调或未深度嵌套的计时器的上下文中设置的计时器不受钳位.

在回调的情况下,setTimeout在另一个setTimeout的上下文中递归调用,因此最小超时为4ms.在promise的情况下,setTimeout实际上不是递归调用的,所以最小超时为0(实际上不会是0,因为其他东西也必须运行).

那么我们怎么知道setTimeout是递归调用的呢?好吧,我们可以在jsperf或只使用benchmark.js进行实验:

// async test
deferred.resolve()
Run Code Online (Sandbox Code Playgroud)

这将导致Uncaught RangeError: Maximum call stack size exceeded.这意味着,一旦调用deferred.resolve,测试就会在同一个tick/stack上再次运行.所以在回调的情况下,setTimeout在它自己的调用上下文中被调用并嵌套在另一个setTimeout中,这将把最小超时设置为4ms.

但是在promise的情况下,.then根据promise规范在下一个tick之后调用callback,并且v8 在下一个tick之后不使用setTimeout调用回调.它使用的东西,必须类似于process.nextTick在或的NodeJS setImmediate,而不是setTimeout的.这会将setTimeout嵌套级别重置为0并使setTimeout延迟0ms.