이전 기사에서 우리는 이벤트 루프가 동기 및 비동기 코드 실행을 조정하는 데 사용되는 Node.js의 핵심 부분이라는 것을 배웠습니다.
6개의 서로 다른 대기열로 구성됩니다. nextTick 큐와 Promise 큐(마이크로태스크 큐라고 함), 타이머 큐, I/O 큐, 검사 큐, 그리고 마지막으로 종료 큐입니다. [관련 튜토리얼 권장 사항: nodejs 비디오 튜토리얼, 프로그래밍 교육]
각 루프에서 콜백 함수는 적절한 경우 대기열에서 제거되고 호출 스택에서 실행됩니다. 이 기사부터 시작하여 이벤트 루프에 대한 이해가 올바른지 확인하기 위해 몇 가지 실험을 실행할 것입니다.
첫 번째 실험 세트는 nextTick 대기열과 Promise 대기열에 중점을 둘 것입니다. 하지만 실험에 들어가기 전에 콜백 함수를 대기열에 추가하는 방법을 이해해야 합니다.
참고: 모든 실험은 CommonJS 모듈 형식을 사용하여 수행되었습니다.
nextTick 큐에 콜백 함수를 추가하려면 내장된 process.nextTick()
메서드를 사용할 수 있습니다. 구문은 매우 간단합니다: process.nextTick(callbackFn)
. 메서드가 호출 스택에서 실행되면 콜백 함수가 nextTick 대기열에 추가됩니다. process.nextTick()
方法。语法很简单:process.nextTick(callbackFn)
。当该方法在调用栈上执行时,回调函数将被加入到 nextTick 队列中。
要将回调函数加入到 Promise 队列中,我们需要使用 Promise.resolve().then(callbackFn)
。当 Promise 解决时(resolve),传递给 then()
的回调函数将被入队到 Promise 队列中。
现在我们已经了解了如何向两个队列添加回调函数,让我们开始第一个实验吧。
// index.js console.log("console.log 1"); process.nextTick(() => console.log("this is process.nextTick 1")); console.log("console.log 2");
这段代码,记录了三个不同的语句。第二个语句使用 process.nextTick()
将回调函数排入 nextTick 队列。
第一个 console.log()
语句被推入调用栈中执行。它在控制台中记录相应的消息,然后弹出堆栈。
接下来,在调用栈上执行 process.nextTick()
,将回调函数入队到 nextTick 队列,并弹出。此时仍有用户编写的代码需要执行,因此回调函数必须等待其轮到。
执行继续进行,最后一个 console.log()
语句被推入堆栈中,该消息记录在控制台中,并将该函数从调用栈中弹出。现在,没有更多的用户编写同步代码需要执行,因此控制权进入事件循环。
nextTick 队列中的回调函数被推入调用栈,console.log()
被推入调用栈并且执行,在控制台记录相应的消息。
console.log 1 console.log 2 this is process.nextTick 1
所有用户编写的同步 JavaScript 代码优先于异步代码执行。
让我们继续进行第二个实验。
// index.js Promise.resolve().then(() => console.log("this is Promise.resolve 1")); process.nextTick(() => console.log("this is process.nextTick 1"));
我们有一个 Promise.resolve().then() 调用和一个 process.nextTick() 调用。
当调用栈执行第 1 行时,它将回调函数排队到 Promise 队列中;当调用栈执行第 2 行时,它将回调函数排队到 nextTick 队列中。
第 2 行后没有代码需要执行。
控制权进入事件循环,在其中 nextTick 队列优先于 Promise 队列(这是 Node.js 运行时的工作方式)。
事件循环执行 nextTick 队列回调函数,然后再执行 Promise 队列回调函数。
控制台显示“this is process.nextTick 1”,然后是“this is Promise.resolve 1”。
this is process.nextTick 1 this is Promise.resolve 1
nextTick 队列中的所有回调函数优先于 Promise 队列中的回调函数执行。
让我带你走一遍上述第二个实验的更详细版本。
// index.js process.nextTick(() => console.log("this is process.nextTick 1")); process.nextTick(() => { console.log("this is process.nextTick 2"); process.nextTick(() => console.log("this is the inner next tick inside next tick") ); }); process.nextTick(() => console.log("this is process.nextTick 3")); Promise.resolve().then(() => console.log("this is Promise.resolve 1")); Promise.resolve().then(() => { console.log("this is Promise.resolve 2"); process.nextTick(() => console.log("this is the inner next tick inside Promise then block") ); }); Promise.resolve().then(() => console.log("this is Promise.resolve 3"));
该代码包含三个 process.nextTick()
调用和三个 Promise.resolve()
Promise.resolve().then(callbackFn)
을 사용해야 합니다. Promise가 해결되면 then()
에 전달된 콜백 함수가 Promise 대기열에 추가됩니다. 🎜🎜이제 두 개의 대기열에 콜백 함수를 추가하는 방법을 알았으니 첫 번째 실험을 시작하겠습니다. 🎜this is process.nextTick 1 this is process.nextTick 2 this is process.nextTick 3 this is the inner next tick inside next tick this is Promise.resolve 1 this is Promise.resolve 2 this is Promise.resolve 3 this is the inner next tick inside Promise then block🎜이 코드는 세 가지 다른 문장을 기록합니다. 두 번째 문은
process.nextTick()
을 사용하여 콜백 함수를 nextTick 대기열에 넣습니다. 🎜console.log ()
명령문은 실행을 위해 호출 스택으로 푸시됩니다. 콘솔에 적절한 메시지를 기록한 다음 스택을 팝합니다. 🎜🎜다음으로 호출 스택에서 process.nextTick()
을 실행하고 콜백 함수를 nextTick 대기열에 추가한 후 팝업합니다. 이 시점에서 실행해야 하는 사용자 작성 코드가 아직 남아 있으므로 콜백 함수는 차례를 기다려야 합니다. 🎜🎜실행이 계속되고 마지막 console.log()
문이 스택에 푸시되고 메시지가 콘솔에 기록되며 함수가 호출 스택에서 제거됩니다. 이제 실행할 사용자 작성 동기 코드가 더 이상 없으므로 제어가 이벤트 루프로 들어갑니다. 🎜🎜nextTick 큐의 콜백 함수가 콜 스택에 푸시되고 console.log()
가 콜 스택에 푸시되어 실행되며 해당 메시지가 콘솔에 기록됩니다. 🎜rrreee🎜사용자가 작성한 모든 동기 JavaScript 코드는 비동기 코드보다 먼저 실행됩니다. 🎜🎜두 번째 실험으로 넘어가겠습니다. 🎜
🎜nextTick 대기열의 모든 콜백 함수는 Promise 대기열의 콜백 함수보다 먼저 실행됩니다. 🎜🎜위의 두 번째 실험에 대한 더 자세한 버전을 안내해 드리겠습니다. 🎜
process.nextTick()
호출과 세 개의 Promise.resolve()
호출 문이 포함되어 있습니다. 각 콜백 함수는 적절한 메시지를 기록합니다. 🎜但是,第二个 process.nextTick()
和第二个 Promise.resolve()
都有一个额外的 process.nextTick()
语句,每个都带有一个回调函数。
为了加快对此可视化的解释,我将省略调用栈讲解。当调用栈执行所有 6 个语句时,nextTick 队列中有 3 个回调函数,Promise 队列中也有 3 个回调函数。没有其他要执行的内容后,控制权进入事件循环。
我们知道,nextTick 队列中具有高优先级。首先执行第 1 个回调函数,并将相应的消息记录到控制台中。
接下来,执行第 2 个回调函数,记录了第 2 条日志语句。但是,这个回调函数包含另一个对 process.nextTick()
的调用,该方法内的回调函数(译注:即() => console.log("this is the inner next tick inside next tick")
)入队到nextTick 队列末尾。
然后 Node.js 执行第 3 个回调函数并将相应消息记录到控制台上。最初只有 3 个回调,但是因为第 2 次时向nextTick 队列又添加了一个,所以变成 4 个了。
事件循环将第 4 个回调函数推入调用栈,执行 console.log()
语句。
接着处理完 nextTick 队列之后,控制流程进入到 Promise 队列。Promise 队列与 nextTick 队列处理方式类似。
首先记录“Promise.resolve 1”,然后是“Promise.resolve 2”,这时因为调用 process.nextTick() 的原因,一个函数(译注:即 () => console.log("this is the inner next tick inside Promise then block")
) 被推入 nextTick 队列了。尽管如此,控制流程仍停留在 Promise 队列,因此还会继续执行队列中的其他函数。然后我们得到“Promise.resolve 3”,此时 Promise 队列为空。
Node.js 将再次检查微任务队列中是否有新的回调。由于 nextTick 队列中有一个回调,因此会执行这个回调,导致我们的最后一条日志输出。
this is process.nextTick 1 this is process.nextTick 2 this is process.nextTick 3 this is the inner next tick inside next tick this is Promise.resolve 1 this is Promise.resolve 2 this is Promise.resolve 3 this is the inner next tick inside Promise then block
这可能是一个稍微高级的实验,但推论仍然相同。
nextTick 队列中的所有回调函数优先于 Promise 队列中的回调函数执行。
使用 process.nextTick()
时要小心。过度使用此方法可能会导致事件循环饥饿,从而阻止队列中的其余部分运行。当存在大量的 nextTick() 调用时,I/O 队列是无法执行自己的回调函数的。官方文档建议使用 process.nextTick()
的两个主要场景:处理错误或在调用栈为空事件循环继续之前执行回调用。所以在使用 process.nextTick()
时,要明智一些。
实验表明,用户编写的所有同步 JavaScript 代码优先于异步代码执行,并且 nextTick 队列中的所有回调函数优先于 Promise 队列中的回调函数执行。
原文链接:Visualizing nextTick and Promise Queues in Node.js Event Loop,2023年3月30日,by Vishwas Gopinath
更多node相关知识,请访问:nodejs 教程!
위 내용은 노드 이벤트 루프의 마이크로태스크 큐에 대한 심층 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!