setImmediate
。如果要将函数有效地排队在事件队列头部,以便在当前函数完成后立即执行,则使用process.nextTick
。setImmediate
而不是process.nextTick
来排队下一次迭代,否则任何I/O事件回调都无法在迭代之间运行。requestAnimationFrame
,因为它并不总是发生(我肯定见过这种情况,例如当前标签页不是该页面),而且它可能会在页面完成绘制之前被调用(即浏览器仍然在忙于绘制中)。 - robocat以一个例子为说明:
import fs from 'fs';
import http from 'http';
const options = {
host: 'www.stackoverflow.com',
port: 80,
path: '/index.html'
};
describe('deferredExecution', () => {
it('deferredExecution', (done) => {
console.log('Start');
setTimeout(() => console.log('setTimeout 1'), 0);
setImmediate(() => console.log('setImmediate 1'));
process.nextTick(() => console.log('nextTick 1'));
setImmediate(() => console.log('setImmediate 2'));
process.nextTick(() => console.log('nextTick 2'));
http.get(options, () => console.log('network IO'));
fs.readdir(process.cwd(), () => console.log('file system IO 1'));
setImmediate(() => console.log('setImmediate 3'));
process.nextTick(() => console.log('nextTick 3'));
setImmediate(() => console.log('setImmediate 4'));
fs.readdir(process.cwd(), () => console.log('file system IO 2'));
console.log('End');
setTimeout(done, 1500);
});
});
将给出以下输出
Start // synchronous
End // synchronous
nextTick 1 // microtask
nextTick 2 // microtask
nextTick 3 // microtask
setTimeout 1 // macrotask
file system IO 1 // macrotask
file system IO 2 // macrotask
setImmediate 1 // macrotask
setImmediate 2 // macrotask
setImmediate 3 // macrotask
setImmediate 4 // macrotask
network IO // macrotask
我希望这能帮助理解它们之间的差异。
更新:
使用
process.nextTick()
延迟的回调在触发任何其他 I/O 事件之前运行,而使用 setImmediate()的执行会排队等待已经在队列中的任何 I/O 事件。Node.js 设计模式,作者 Mario Casciaro(可能是有关 node.js/js 的最佳书籍)
我认为我可以很好地阐述这一点。由于nextTick
在当前操作的末尾被调用,递归调用可能会阻止事件循环继续运行。setImmediate
通过在事件循环的检查阶段触发来解决这个问题,允许事件循环正常继续。
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
源代码:https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/
请注意,检查阶段紧随轮询阶段之后。这是因为轮询阶段和I/O回调是您调用setImmediate
的最有可能运行的地方。因此,理想情况下,大多数这些调用实际上会相当立即,只是不像nextTick
那样立即,它在每个操作之后都会被检查,并且在事件循环之外存在。
让我们来看一个小例子,介绍setImmediate
和process.nextTick
之间的区别:
function step(iteration) {
if (iteration === 10) return;
setImmediate(() => {
console.log(`setImmediate iteration: ${iteration}`);
step(iteration + 1); // Recursive call from setImmediate handler.
});
process.nextTick(() => {
console.log(`nextTick iteration: ${iteration}`);
});
}
step(0);
nextTick iteration: 0
setImmediate iteration: 0
nextTick iteration: 1
setImmediate iteration: 1
nextTick iteration: 2
setImmediate iteration: 2
nextTick iteration: 3
setImmediate iteration: 3
nextTick iteration: 4
setImmediate iteration: 4
nextTick iteration: 5
setImmediate iteration: 5
nextTick iteration: 6
setImmediate iteration: 6
nextTick iteration: 7
setImmediate iteration: 7
nextTick iteration: 8
setImmediate iteration: 8
nextTick iteration: 9
setImmediate iteration: 9
step
移动到nextTick
处理程序中,而不是setImmediate
。function step(iteration) {
if (iteration === 10) return;
setImmediate(() => {
console.log(`setImmediate iteration: ${iteration}`);
});
process.nextTick(() => {
console.log(`nextTick iteration: ${iteration}`);
step(iteration + 1); // Recursive call from nextTick handler.
});
}
step(0);
step
移动到nextTick
处理程序中,事情的行为将会有所不同。我们的事件循环的第一次迭代运行并调用step
,注册了一个setImmediate
处理程序以及一个nextTick
处理程序。在当前操作结束后,我们的nextTick
处理程序触发,递归调用step
并注册另一个setImmediate
处理程序以及另一个nextTick
处理程序。由于nextTick
处理程序在当前操作之后触发,在nextTick
处理程序中注册一个nextTick
处理程序将导致第二个处理程序在当前处理程序操作完成后立即运行。nextTick
处理程序将继续触发,阻止当前事件循环继续进行。在看到单个setImmediate
处理程序触发之前,我们将通过所有的nextTick
处理程序。nextTick iteration: 0
nextTick iteration: 1
nextTick iteration: 2
nextTick iteration: 3
nextTick iteration: 4
nextTick iteration: 5
nextTick iteration: 6
nextTick iteration: 7
nextTick iteration: 8
nextTick iteration: 9
setImmediate iteration: 0
setImmediate iteration: 1
setImmediate iteration: 2
setImmediate iteration: 3
setImmediate iteration: 4
setImmediate iteration: 5
setImmediate iteration: 6
setImmediate iteration: 7
setImmediate iteration: 8
setImmediate iteration: 9
nextTick
调用将继续递归,从而永远不会让事件循环继续到下一阶段。这就是为什么递归使用nextTick
时可能会变成阻塞状态,而setImmediate
将在下一个事件循环中触发,并且从其中设置另一个setImmediate
处理程序不会中断当前事件循环,允许其像正常情况下执行事件循环的阶段。nextTick
听起来像它要在下一个事件循环中触发,而当前循环的结尾比下一个循环的开始更“即时”。噢,这就是API成熟并且人们依赖现有接口所得到的结果。在回答的评论中,没有明确说明nextTick从宏观语义移到微观语义。
在node 0.9之前(引入setImmediate之前),nextTick在下一个调用栈的开头操作。
自node 0.9以来,nextTick在现有调用栈的末尾操作,而setImmediate在下一个调用栈的开头。
请查看https://github.com/YuzuJS/setImmediate获取工具和详细信息。
这里有一些很好的答案,详细解释了它们的工作原理。
只是添加一个回答特定问题的:
我应该何时使用
nextTick
,何时应该使用setImmediate
?
setImmediate
。Node.js事件循环、计时器和process.nextTick()
文档包括以下内容:
我们建议开发人员在所有情况下都使用
setImmediate()
,因为它更容易理解(并且它产生的代码与更广泛的环境兼容,如浏览器JS)。
在文档的早期警告中,它警告说process.nextTick
可能会导致...
一些糟糕情况,因为它允许您通过递归
process.nextTick()
调用来“耗尽”您的I/O,从而防止事件循环达到poll阶段。
事实证明,process.nextTick
甚至可以耗尽Promises
:
Promise.resolve().then(() => { console.log('this happens LAST'); });
process.nextTick(() => {
console.log('all of these...');
process.nextTick(() => {
console.log('...happen before...');
process.nextTick(() => {
console.log('...the Promise ever...');
process.nextTick(() => {
console.log('...has a chance to resolve');
})
})
})
})
另一方面,setImmediate
更容易被理解,并避免了这些类型的问题:
Promise.resolve().then(() => { console.log('this happens FIRST'); });
setImmediate(() => {
console.log('this happens LAST');
})
因此,除非有特定需要使用process.nextTick
的独特行为,否则建议在所有情况下都使用setImmediate()
。我建议您查看专门介绍Loop的文档部分以获得更好的理解。这里摘录了一些段落:
我们有两个调用对于用户来说非常相似,但它们的名称令人困惑。
process.nextTick() 立即在同一个阶段上触发
setImmediate() 在下一次迭代或事件循环中触发
实际上,这些名称应该交换。process.nextTick() 比 setImmediate() 更快触发,但这是过去的遗留问题,不太可能改变。
process.nextTick()
来中断 CPU 密集型操作你不应该使用 process.nextTick() 来中断这样的操作。这样做会导致一个永远不会清空的微任务队列,你的应用程序将永远被困在同一个阶段中! -- Thomas Hunter. Distributed Systems with Node.js
事件循环的每个阶段都包含多个回调函数:
一旦process.nextTick()
被触发,它将始终保持在相同的阶段。
const nt_recursive = () => process.nextTick(nt_recursive);
nt_recursive(); // setInterval will never run
const si_recursive = () => setImmediate(si_recursive);
si_recursive(); // setInterval will run
setInterval(() => console.log('hi'), 10);
setInterval()
表示应用程序执行的一些异步工作,例如响应传入的 HTTP 请求。
一旦运行 nt_recursive()
函数,应用程序就会得到一个永远不会清空的微任务队列,异步工作永远不会得到处理。
但是替代版本 si_recursive()
没有相同的副作用。
在检查阶段内使用 setImmediate()
调用会将回调添加到下一个事件循环迭代的检查阶段队列中,而不是当前阶段的队列中。
process.nextTick
使函数全部异步化。
function foo(count, callback) {
if (count <= 0) {
return process.nextTick(() => callback(new TypeError('count > 0')));
}
myAsyncOperation(count, callback);
}
setImmediate()
或process.nextTick()
都可以,只要确保不会意外引入递归。——Thomas Hunter.使用Node.js进行分布式系统
nextTick
比setImmediate
更快。 - user3644644setImmediate
的功能。请注意,我的翻译尽可能通俗易懂,但不会改变原意。 - ChevsetImmediate
之前运行,但不能在nextTick
之前运行,这是正确的吗? - user663031