process.nextTick 和 queueMicrotask 的区别
Posted
技术标签:
【中文标题】process.nextTick 和 queueMicrotask 的区别【英文标题】:Difference between process.nextTick and queueMicrotask 【发布时间】:2019-08-23 07:44:36 【问题描述】:节点 11.0.0 添加 queueMicrotasks
作为实验。 doc 表示它类似于 process.nextTick
,但队列由 V8 而不是 Node.js 管理。使用queueMicrotasks
而不是process.nextTick
的用例是什么?使用一个比另一个有任何性能提升吗?
【问题讨论】:
重复***.com/questions/25915634/… @VikashSingh 这不是重复的。我在问之前检查了那个问题。它解释了微任务和宏任务之间的区别。但根据接受的答案,process.nextTick
也属于微任务列表
【参考方案1】:
队列
我们可以找到不同的队列(在完成函数/脚本执行后以检查优先级顺序显示):
nextTick 微任务 计时器(已过期) 立即如何检查队列?
首先,检查 nextTick 队列以获取执行任务,一旦耗尽,检查 microTasks 队列。完成微任务队列中的任务后检查的过程 重复 nextTick 和 microTasks 队列,直到队列被清空。
下一个要检查的队列是定时器一,最后是立即队列。
区别
都是一样的,方式都是在当前函数或脚本执行完之后再执行一个任务。
他们有不同的队列。 nextTick 的队列由 node 管理,microtask one 由 v8 管理。
是什么意思?
在当前函数/脚本执行之后首先检查 nextTick 队列,然后是 microTask 队列。
没有性能提升,不同的是在函数/脚本执行后将首先检查 nextTick 队列,必须考虑到这一点。 如果您从不使用 nextTick 而只使用 queueMicrotask,您将获得与仅使用 nextTick 相同的行为(考虑到您的任务将与其他微任务一起放入队列中)
用例可能是在任何微任务之前执行任务,例如,在承诺 then 和/或 catch 之前。值得注意的是,promise 使用的是 microtask,所以任何添加到 then/catch 的回调都会被添加到 microtask 队列中,并在 nextTick 队列为空时执行。
示例
这段代码执行后:
function task1()
console.log('promise1 resolved');
function task2()
console.log('promise2 resolved');
process.nextTick(task10);
function task3()
console.log('promise3 resolved');
function task4()
console.log('immediate 1');
function task5()
console.log('tick 1');
function task6()
console.log('tick 2');
function task7()
console.log('microtask 1');
function task8()
console.log('timeout');
function task9()
console.log('immediate 2');
function task10()
console.log('tick3');
queueMicrotask(task11);
function task11()
console.log('microtask 2');
Promise.resolve().then(task1);
Promise.resolve().then(task2);
Promise.resolve().then(task3);
setImmediate(task4);
process.nextTick(task5);
process.nextTick(task6);
queueMicrotask(task7);
setTimeout(task8, 0);
setImmediate(task9);
执行
nextTick:任务5 |任务6 微任务:task1 |任务2 |任务3 |任务7 定时器:task8 立即:任务4 |任务9第一步:执行 nextTick 队列中的所有任务
nextTick:空 微任务:task1 |任务2 |任务3 |任务7 定时器:task8 立即:任务4 |任务9输出:
勾选 1 勾选 2第 2 步:执行 microTasks 队列中的所有任务
nextTick: task10 微任务:空 定时器:task8 立即:任务4 |任务9输出:
勾选 1 勾选 2 承诺 1 已解决 承诺 2 已解决 承诺 3 已解决 微任务 1第三步:执行nextTick队列中的所有任务(微任务(task2)的执行增加了一个新任务)
nextTick:空 微任务:task11 定时器:task8 立即:任务4 |任务9输出:
勾选 1 勾选 2 承诺 1 已解决 承诺 2 已解决 承诺 3 已解决 微任务 1 勾选 3第四步:执行microTasks队列中的所有任务(task10的执行增加了一个新任务)
nextTick:空 微任务:空 定时器:task8 立即:任务4 |任务9输出:
勾选 1 勾选 2 承诺 1 已解决 承诺 2 已解决 承诺 3 已解决 微任务 1 勾选 3 微任务 2第 5 步:nextTick 和 microTasks 队列中不再有任务,下一个执行计时器队列。
nextTick:空 微任务:空 定时器:空 立即:任务4 |任务9输出:
勾选 1 勾选 2 承诺 1 已解决 承诺 2 已解决 承诺 3 已解决 微任务 1 勾选 3 微任务 2 超时第 6 步:(过期)计时器队列中没有更多任务,执行立即队列中的任务
nextTick:空 微任务:空 定时器:空 立即:空输出:
勾选 1 勾选 2 承诺 1 已解决 承诺 2 已解决 承诺 3 已解决 微任务 1 勾选 3 微任务 2 超时 立即 1 立即2正如我们所见,选择一个或另一个没有性能原因,所选择的决定取决于我们的需求以及需要做什么以及何时完成。
想象一下这段代码:
let i = 1;
queueMicrotask(() => console.log(i));
process.nextTick(() => i++);
由于首先检查 nextTick 队列,因此输出将为 2。
如果你这样做了
let i = 1;
queueMicrotask(() => console.log(i));
process.nextTick(() => queueMicrotask(() =>i++));
你会得到 1 个。
通过示例,我想让您了解用例来自您对执行任务的内容和时间的需求。重要的是要考虑到 promise 中的 then/catch 回调是微任务,将在 nextTick 任务之后执行,考虑到这对于避免错误很重要(如后面的示例所述)。
【讨论】:
我在这些单独的 nextTick 和 microTasks 队列中看到的一个有趣的用例是安排一个任务在 DataLoader 中的所有微任务之后立即运行 github.com/graphql/dataloader/blob/… 请注意,与超时相关的即时时间似乎是不确定的。在微任务、nextTick 作业或即时任务中设置一些零超时和一些立即数似乎在超时之前、之后或中间执行所有立即数(作为一个块) 实际上上下文无关紧要,多次将其作为脚本运行:let msg = []; for(let i = 0; i < 20; i++) setImmediate(()=>msg.push('i'+i)); setTimeout(()=>msg.push('T'+i)); process.on('exit',()=>console.log(...msg));
已经为我展示了这种现象(多核 x86_64 上 linux 上的节点 v12.18.2)跨度>
解释正确。但是,您能否对这个图表有所了解:nodejs.org/en/docs/guides/event-loop-timers-and-nexttick?这似乎与您的解释不符。
@Praveen 是对的。您的回答非常出色,但 Node.js 官方文档上的图表将计时器放在了顶部。你能解释一下吗?以上是关于process.nextTick 和 queueMicrotask 的区别的主要内容,如果未能解决你的问题,请参考以下文章
译Node.js 事件循环, 定时器, 和 process.nextTick()
Node.js 中 process.nextTick 的正确用例是啥?
process.nextTick(function() throw err; ) 错误:getaddrinfo ENOTFOUND 节点节点:27017 - nodejs
译Node.js的eventloop,timers和process.nextTick()