>웹 프론트엔드 >JS 튜토리얼 >노드 이벤트 루프의 마이크로태스크 큐에 대한 심층 분석

노드 이벤트 루프의 마이크로태스크 큐에 대한 심층 분석

青灯夜游
青灯夜游앞으로
2023-04-13 17:39:151847검색

노드 이벤트 루프의 마이크로태스크 큐에 대한 심층 분석

이전 기사에서 우리는 이벤트 루프가 동기 및 비동기 코드 실행을 조정하는 데 사용되는 Node.js의 핵심 부분이라는 것을 배웠습니다.

6개의 서로 다른 대기열로 구성됩니다. nextTick 큐와 Promise 큐(마이크로태스크 큐라고 함), 타이머 큐, I/O 큐, 검사 큐, 그리고 마지막으로 종료 큐입니다. [관련 튜토리얼 권장 사항: nodejs 비디오 튜토리얼, 프로그래밍 교육]

각 루프에서 콜백 함수는 적절한 경우 대기열에서 제거되고 호출 스택에서 실행됩니다. 이 기사부터 시작하여 이벤트 루프에 대한 이해가 올바른지 확인하기 위해 몇 가지 실험을 실행할 것입니다.

첫 번째 실험 세트는 nextTick 대기열과 Promise 대기열에 중점을 둘 것입니다. 하지만 실험에 들어가기 전에 콜백 함수를 대기열에 추가하는 방법을 이해해야 합니다.

참고: 모든 실험은 CommonJS 모듈 형식을 사용하여 수행되었습니다.

Enqueue 콜백 함수

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() 调用。

可视化

assets_YJIGb4i01jvw0SRdL5Bt_b32c606155c14ecfa60c8dc102f3bcf0_compressed (1).gif

当调用栈执行第 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 대기열에 콜백 함수를 추가하려면 Promise.resolve().then(callbackFn)을 사용해야 합니다. Promise가 해결되면 then()에 전달된 콜백 함수가 Promise 대기열에 추가됩니다. 🎜🎜이제 두 개의 대기열에 콜백 함수를 추가하는 방법을 알았으니 첫 번째 실험을 시작하겠습니다. 🎜

실험 1🎜

코드

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 대기열에 넣습니다. 🎜

시각화

🎜노드 이벤트 루프의 마이크로태스크 큐에 대한 심층 분석🎜🎜firstconsole.log () 명령문은 실행을 위해 호출 스택으로 푸시됩니다. 콘솔에 적절한 메시지를 기록한 다음 스택을 팝합니다. 🎜🎜다음으로 호출 스택에서 process.nextTick()을 실행하고 콜백 함수를 nextTick 대기열에 추가한 후 팝업합니다. 이 시점에서 실행해야 하는 사용자 작성 코드가 아직 남아 있으므로 콜백 함수는 차례를 기다려야 합니다. 🎜🎜실행이 계속되고 마지막 console.log() 문이 스택에 푸시되고 메시지가 콘솔에 기록되며 함수가 호출 스택에서 제거됩니다. 이제 실행할 사용자 작성 동기 코드가 더 이상 없으므로 제어가 이벤트 루프로 들어갑니다. 🎜🎜nextTick 큐의 콜백 함수가 콜 스택에 푸시되고 console.log()가 콜 스택에 푸시되어 실행되며 해당 메시지가 콘솔에 기록됩니다. 🎜rrreee

결과

🎜사용자가 작성한 모든 동기 JavaScript 코드는 비동기 코드보다 먼저 실행됩니다. 🎜
🎜두 번째 실험으로 넘어가겠습니다. 🎜

실험 2🎜

코드

rrreee🎜Promise.resolve()가 있습니다. .then() 호출 및 process.nextTick() 호출. 🎜

시각화

🎜assets_YJIGb4i01jvw0SRdL5Bt_b32c606155c14ecfa60c8dc102f3bcf0_compressed (1).gif🎜🎜콜 스택이 실행될 때 1, 호출 스택이 실행될 때 콜백 함수를 Promise 대기열에 넣습니다. 2줄을 실행하면 콜백 함수를 nextTick 대기열에 넣습니다. 🎜🎜라인 2 이후에는 코드를 실행할 필요가 없습니다. 🎜🎜Control은 이벤트 루프로 들어가며, 여기서 nextTick 대기열이 Promise 대기열보다 우선합니다(이것이 Node.js 런타임이 작동하는 방식입니다). 🎜🎜이벤트 루프는 nextTick 큐 콜백 함수를 실행한 다음 Promise 큐 콜백 함수를 실행합니다. 🎜🎜콘솔에 "이것은 process.nextTick 1입니다"라고 표시된 다음 "이것은 Promise.resolve 1입니다"라고 표시됩니다. 🎜rrreee

결과

🎜nextTick 대기열의 모든 콜백 함수는 Promise 대기열의 콜백 함수보다 먼저 실행됩니다. 🎜
🎜위의 두 번째 실험에 대한 더 자세한 버전을 안내해 드리겠습니다. 🎜

복지 실험🎜

코드

rrreee🎜The 코드에는 세 개의 process.nextTick() 호출과 세 개의 Promise.resolve() 호출 문이 포함되어 있습니다. 각 콜백 함수는 적절한 메시지를 기록합니다. 🎜

但是,第二个 process.nextTick() 和第二个 Promise.resolve() 都有一个额外的 process.nextTick() 语句,每个都带有一个回调函数。

可视化

assets_YJIGb4i01jvw0SRdL5Bt_b32c606155c14ecfa60c8dc102f3bcf0_compressed (2).gif

为了加快对此可视化的解释,我将省略调用栈讲解。当调用栈执行所有 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.cn에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제