我想我可以很好地说明这一点。由于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);
假设我们刚刚运行了这个程序,并且正在逐步执行事件循环的第一次迭代。它将调用迭代为零的step 函数。然后它将注册两个处理程序,一个用于setImmediate,一个用于process.nextTick。然后我们从 setImmediate 处理程序中递归调用此函数,该处理程序将在下一个检查阶段运行。 nextTick 处理程序将在当前操作结束时运行并中断事件循环,因此即使它是第二个注册的,它实际上也会首先运行。
最终的顺序是:nextTick 在当前操作结束时触发,下一个事件循环开始,正常的事件循环阶段执行,setImmediate 触发并递归调用我们的 step 函数以重新开始该过程。当前操作结束,nextTick 火灾等。
上述代码的输出将是:
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 注册setImmedaite 处理程序以及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
请注意,如果我们没有中断递归调用并在 10 次迭代后中止它,那么 nextTick 调用将继续递归并且永远不会让事件循环继续到下一个阶段。这就是nextTick 在递归使用时可能会阻塞的原因,而setImmediate 将在下一个事件循环中触发,并且从一个内部设置另一个setImmediate 处理程序根本不会中断当前事件循环,从而允许它继续执行阶段事件循环正常。
希望有帮助!
PS - 我同意其他评论者的观点,即这两个函数的名称可以很容易地互换,因为 nextTick 听起来它会在下一个事件循环中触发,而不是在当前事件循环的结尾,以及当前循环比下一个循环的开始更“立即”。哦,好吧,随着 API 的成熟和人们开始依赖现有的接口,这就是我们所得到的。