NodeJS - setTimeout(fn,0) vs setImmediate(fn)

131
这两个之间有什么区别,我应该在何时使用其中一个而不是另一个?

3
相关 setImmediate Vs nextTick:在Node.js中,setImmediatenextTick都是用于异步执行代码的函数。两者之间的区别在于它们被添加到事件循环队列的不同位置。setImmediate将回调添加到下一个事件循环迭代中,在I/O事件之后执行,而nextTick则在当前操作完成后立即执行,但在I/O事件之前。简言之,nextTick总是在setImmediate之前执行,因为它是在当前操作完成后立即执行,而setImmediate要等待一个新的事件循环迭代。 - Benjamin Gruenbaum
1
我今天学到了一个事实,就是setImmediate()在Chrome中完全缺失。 - Brain2000
9个回答

105

setTimeout的功能类似于在延迟完成后调用函数。每当调用函数时,它不会立即执行,而是排队等待所有当前执行和已排队的事件处理程序先完成执行。setTimeout(,0)基本上意味着在当前队列中的所有函数都执行完毕后再执行。无法保证需要多长时间。

setImmediate在这方面也类似,但它不使用函数队列,而是检查I/O事件处理程序队列。如果当前快照中的所有I/O事件都被处理,它就会执行回调函数。它会将它们立即排队在最后一个I/O处理程序之后,有点像process.nextTick。因此速度更快。

另外,(setTimeout,0)会很慢,因为它将在执行前至少检查一次计时器。有时会比正常情况慢两倍。下面是一个基准测试结果。

var Suite = require('benchmark').Suite
var fs = require('fs')

var suite = new Suite

suite.add('deffered.resolve()', function(deferred) {
  deferred.resolve()
}, {defer: true})

suite.add('setImmediate()', function(deferred) {
  setImmediate(function() {
    deferred.resolve()
  })
}, {defer: true})

suite.add('setTimeout(,0)', function(deferred) {
  setTimeout(function() {
    deferred.resolve()
  },0)
}, {defer: true})

suite
.on('cycle', function(event) {
  console.log(String(event.target));
})
.on('complete', function() {
  console.log('Fastest is ' + this.filter('fastest').pluck('name'));
})
.run({async: true})

输出

deffered.resolve() x 993 ops/sec ±0.67% (22 runs sampled)
setImmediate() x 914 ops/sec ±2.48% (57 runs sampled)
setTimeout(,0) x 445 ops/sec ±2.79% (82 runs sampled)

第一个示例展示了最快可能的调用方法。您可以自行验证如果与其他方法相比,setTimeout被调用的次数减半。同时请记住setImmediate会根据您的文件系统调用进行调整,在负载较重时表现会受到影响。我认为setTimeout无法做得更好。

setTimeout是一种在一定时间后调用函数的非侵入式方式,就像在浏览器中一样。它可能不适合于服务器端(想想为什么我使用了benchmark.js而不是setTimeout)。


4
需要注意的是,如果嵌套超过五层,setTimeout函数将会被强制延迟至少四毫秒。请参见HTML规范 - Jack Allan
这个来源(来自另一个答案)似乎驳斥了一些在这里的陈述:http://voidcanvas.com/setimmediate-vs-nexttick-vs-settimeout/ - Dmitri Zaitsev

26
关于事件循环如何工作并消除一些误解的优秀文章。 http://voidcanvas.com/setimmediate-vs-nexttick-vs-settimeout/ 引用该文章: setImmediate回调在I/O队列回调完成或超时后调用。 setImmediate回调放置在Check队列中,在I/O队列之后处理。 setTimeout(fn, 0)回调放置在计时器队列中,并且将在I/O回调以及Check队列回调之后调用。由于事件循环在每个迭代中首先处理计时器队列,因此哪个先执行取决于事件循环处于哪个阶段。

1
setTimeout队列在I/O回调之前被处理。参考:https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/ - human

6

setImmediate() 用于在 I/O 事件回调函数执行完后、定时器之前立即执行 callback 函数。

setTimeout() 用于在 delay 毫秒后执行一次 callback 函数。

文档中是这么说的。

setTimeout(function() {
  console.log('setTimeout')
}, 0)

setImmediate(function() {
  console.log('setImmediate')
})

如果您运行上面的代码,结果将会像这样...即使当前文档说明“在I/O事件回调之后、setTimeout和setInterval之前安排“立即”执行回调函数。”
结果如下:
setTimeout setImmediate 如果您将您的示例嵌套在另一个定时器中,则总是先打印setImmediate,然后是setTimeout。
setTimeout(function() {
  setTimeout(function() {
    console.log('setTimeout')
  }, 0);
  setImmediate(function() {
    console.log('setImmediate')
  });
}, 10);

那么你什么时候会更喜欢其中的一个呢? - Shlomi Schwartz
31
你没有解释为什么你展示的事情会发生。这个回答对我没有帮助。 - Clint Eastwood
3
在你的第一个结果中,请解释为什么 setTimeout 在 setImmediate 之前先执行了。 在Node.js中,setTimeout和setImmediate是两个不同的API,它们都用于调度任务。当一段代码执行时,Node.js会将其放入一个事件循环队列中,然后按照一定的顺序调度这些任务。setTimeout的调度时间是经过一段延迟后执行,而setImmediate则是在当前循环结束后立即执行。因此,如果在一个事件循环中同时调用了setTimeout和setImmediate,setTimeout会被放入延迟队列中,setImmediate会被放入check队列中。当当前循环结束后,Node.js会先执行check队列中的任务,再执行延迟队列中的任务,因此setTimeout会先执行。 - Agus Syahputra
2
@AgusSyahputra 看看这个:https://github.com/nodejs/node-v0.x-archive/issues/25788 - Rodrigo Branas
1
SetImmediate 不会在所有情况下都优先于 setTimeout 和 setInterval 执行。 - Midhun G S
显示剩余2条评论

2

除非您确实需要 setTimeout(,0)(但我甚至无法想象有什么用处),否则始终使用 setImmediatesetImmediate 回调几乎总是在 setTimeout(,0) 之前执行,除非在第一个 tick 中调用和 setImmediate 回调中。


2
我认为使用setTimeout而不是setImmediate的主要原因是您的代码需要在没有实现setImmediate的浏览器中执行。即使如此,您也可以创建一个shim。 - Gregory Magarshak
12
这是错误的建议。如果每个任务都要求优先执行,那么异步执行的性能特征将与队列末尾排队相比较差。setTimeout 应该是首选方法,只有在必要时才使用 setImmediate - Rich Remer

0

我认为 Navya S 的答案不正确,这是我的测试代码:

let set = new Set();

function orderTest() {
  let seq = [];
  let add = () => set.add(seq.join());
  setTimeout(function () {
    setTimeout(function () {
      seq.push('setTimeout');
      if (seq.length === 2) add();
    }, 0);

    setImmediate(function () {
      seq.push('setImmediate');
      if (seq.length === 2) add();
    });
  }, 10);
}

// loop 100 times
for (let i = 0; i < 100; i++) {
  orderTest();
}

setTimeout(() => {
  // will print one or two items, it's random
  for (item of set) {
    console.log(item);
  }
}, 100);

解释在这里


0
为了深入理解它们,请先浏览事件循环阶段。
SetImmediate: 它在“check”阶段执行。检查阶段是在I/O阶段之后调用的。
SetTimeOut: 它在“timer”阶段执行。计时器阶段是第一个阶段,但也是在I/O阶段和检查阶段之后调用的。
为了以确定性方式获取输出,取决于事件循环处于哪个阶段;因此,我们可以使用两个函数之一。

0

在大量更新时,可以使用setTimeout(fn,0)来防止浏览器冻结。例如,在websocket.onmessage中,您可能会有HTML更改,如果消息不断到来,则在使用setImmediate时浏览器可能会冻结。


-1
当Javascript引擎开始执行时,它会逐行检查代码。
setTimeout(function() {
  console.log('setTimeout')
}, 0)

setImmediate(function() {
  console.log('setImmediate')
})

当涉及编程时

settimeout会从调用堆栈移动到调用队列并开始执行计时器。

setimmediate会从调用堆栈移动到宏队列(即在第一个循环完成后立即开始执行)

因此,如果settimeout值为0,则在调用堆栈循环完成之前,它将完成其计时器。

这就是为什么settimeout将在setimmediate之前打印的原因。

现在,假设

setTimeout(function() {
  setTimeout(function() {
    console.log('setTimeout')
  }, 0);
  setImmediate(function() {
    console.log('setImmediate')
  });
}, 10);

这意味着,首先主要超时移动到调用队列。同时,调用堆栈完成其执行。

因此,在10毫秒后,函数进入调用堆栈,它将直接执行setimmediate。因为调用堆栈已经空闲以执行任务。


2
根据文档,setImmediate 是宏任务而不是微任务。https://nodejs.dev/learn/understanding-setimmediate - Sergey

-5

使用setImmediate()来避免阻塞事件循环。回调函数将在下一个事件循环中运行,一旦当前事件循环完成。

使用setTimeout()来进行可控延迟。该函数将在指定的延迟时间后运行。最小延迟为1毫秒。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接