>  기사  >  웹 프론트엔드  >  그림과 텍스트의 조합은 Nodejs의 이벤트 루프를 이해하는 데 도움이 됩니다.

그림과 텍스트의 조합은 Nodejs의 이벤트 루프를 이해하는 데 도움이 됩니다.

青灯夜游
青灯夜游앞으로
2021-09-30 10:22:382272검색

이 글은 Nodejs의 이벤트 루프를 이해하는 데 도움이 되도록 그림과 텍스트를 조합하여 사용합니다.

그림과 텍스트의 조합은 Nodejs의 이벤트 루프를 이해하는 데 도움이 됩니다.

다음 전문은 7,000자입니다. 생각이 명확하고 에너지가 넘칠 때 읽어주세요. 이해하신 후에도 오랫동안 잊지 않으실 것이라고 장담합니다. [추천학습: "nodejs Tutorial"]

그림과 텍스트의 조합은 Nodejs의 이벤트 루프를 이해하는 데 도움이 됩니다.

Node event loop

Nodelibuv 하단에 사용된 언어는 C++ 언어입니다. 이는 기본 운영 체제를 작동하는 데 사용되며 운영 체제의 인터페이스를 캡슐화합니다. Node의 이벤트 루프도 libuv로 작성되어 있으므로 Node의 수명주기는 여전히 브라우저의 수명주기와 다릅니다.

Node는 운영 체제를 다루기 때문에 이벤트 루프는 상대적으로 복잡하고 고유한 API를 가지고 있습니다.
이벤트 루프는 운영 체제마다 미묘한 차이가 있습니다. 여기에는 당분간 나열되지 않은 운영 체제에 대한 지식이 포함됩니다. 이번에는 JS 메인 스레드에서 Node의 동작 과정만 소개합니다. Node의 다른 스레드는 당분간 확장되지 않습니다.

이벤트 루프 다이어그램

좋은 그림이고 아무것도 팔지 않습니다. 아래 그림을 이해하고 나면 이벤트 루프를 배우게 됩니다.

그림과 텍스트의 조합은 Nodejs의 이벤트 루프를 이해하는 데 도움이 됩니다.

이벤트 루프 다이어그램

그림과 텍스트의 조합은 Nodejs의 이벤트 루프를 이해하는 데 도움이 됩니다.

이벤트 루프 다이어그램 - 구조

모든 사람에게 전체 보기를 제공하기 위해 디렉토리 구조 다이어그램을 앞에 게시하겠습니다.

그림과 텍스트의 조합은 Nodejs의 이벤트 루프를 이해하는 데 도움이 됩니다.

Directory

계속 자세히 얘기해보자

메인 스레드

그림과 텍스트의 조합은 Nodejs의 이벤트 루프를 이해하는 데 도움이 됩니다.

메인 스레드

위 그림에서 여러 색상 블록의 의미:

  • main: 항목 시작 파일, 실행 메인 기능main:启动入口文件,运行主函数
  • event loop:检查是否要进入事件循环
    • 检查其他线程里是否还有待处理事项
    • 检查其他任务是否还在进行中(比如计时器、文件读取操作等任务是否完成)
    • 有以上情况,进入事件循环,运行其他任务
      事件循环的过程:沿着从timers到close callbacks这个流程,走一圈。到event loop看是否结束,没结束再走一圈。
  • over
  • 이벤트 루프: 이벤트 루프 진입 여부 확인

다른 스레드에 대기 중인 항목이 있는지 확인다른 작업이 아직 진행 중인지 확인(타이머 등) , 파일 읽기 작업 작업이 완료되기를 기다리는 중)위 상황에서 이벤트 루프에 들어가서 다른 작업을 실행하세요

이벤트 루프의 프로세스: 타이머부터 콜백을 닫고 원을 그리며 걷는 과정을 따릅니다. 이벤트 루프로 이동하여 끝났는지 확인하세요. 그렇지 않은 경우 다시 돌아보세요.

그림과 텍스트의 조합은 Nodejs의 이벤트 루프를 이해하는 데 도움이 됩니다.

over: 모든 것이 끝났습니다. 끝났습니다

이벤트 루프 원

이벤트 루프 원그림의 회색 원은 운영 체제와 관련이 있습니다. 는 이 장의 분석의 초점이 아닙니다. 노란색과 주황색 원, 그리고 중앙의 주황색 상자에 집중하세요.

  • 우리는 이벤트 루프의 각 주기를 "루프"("폴" 또는 "틱"이라고도 함)라고 부릅니다.

  • 주기는 6단계를 거칩니다.

  • 타이머: 타이머(setTimeout, setInterval 등과 같은 콜백 함수가 내부에 저장됨)

  • 보류 중인 콜백
  • 유휴 prepare

  • poll: 폴링 대기열(타이머 및 확인 이외의 콜백은 여기에 저장됨)

그림과 텍스트의 조합은 Nodejs의 이벤트 루프를 이해하는 데 도움이 됩니다.

check: 단계 확인(setImmediate를 사용하는 콜백은 이 대기열에 직접 입력됩니다.)

🎜🎜🎜🎜 콜백 닫기🎜 🎜🎜🎜🎜🎜🎜이번에는 위에서 빨간색으로 표시된 세 가지 핵심 사항에만 중점을 둡니다. 🎜

작동 방식

  • 모든 스테이지는 이벤트 대기열을 유지합니다. 각 원을 이벤트 대기열로 생각할 수 있습니다.
  • 브라우저에는 최대 2개의 대기열(매크로 대기열과 마이크로 대기열)이 있습니다. 하지만 노드에는 6개의 큐가 있습니다. 큐에 도착하면 큐에 실행해야 할 작업이 있는지(즉, 콜백 함수가 있는지 확인) 확인하세요. 있는 경우 모든 실행이 완료되고 대기열이 지워질 때까지 순차적으로 실행합니다.
  • 할 일이 없으면 다음 대기열로 이동하여 확인하세요. 모든 대기열을 확인할 때까지 폴링으로 계산됩니다.
  • 그 중에
  • queue.
  • timerspending callbackidle prepare等执行完毕后,到达poll

타이머 대기열 작동 방식timers는 실제 대기열이 아니며 타이머를 내부에 저장합니다.

이 대기열에 도달할 때마다 타이머 스레드의 모든 타이머가 시간순으로 정렬됩니다.


확인 과정: 각 타이머를 순차적으로 계산하고, 타이머가 카운트를 시작한 시간부터 현재 시간까지의 시간이 타이머 간격 매개변수 설정을 충족하는지 계산합니다(예: 1000ms, 타이머가 시작된 시간부터 1m가 있는지 계산). 지금까지 계산) ). 타이머 확인이 통과되면 콜백 함수가 실행됩니다.

폴 큐 작동 방식

폴에 실행해야 할 콜백 함수가 있는 경우 큐가 지워질 때까지 콜백이 순차적으로 실행됩니다.
  • 폴링에 실행해야 할 콜백 함수가 없으면 대기열은 이미 비어 있습니다. 콜백이 다른 대기열에 나타날 때까지 여기에서 기다립니다.
  • 콜백이 다른 대기열에 나타나면 폴링에서 위로 이동하여 이 단계를 종료하고 다음 단계로 들어갑니다.
    • 다른 대기열에 콜백이 없으면 작업을 시작하기 전에 어떤 대기열에서 콜백이 발생할 때까지 폴 대기열에서 계속 기다리세요. (게으른 사람은 이렇게 합니다)

이벤트 프로세스 정리 예
setTimeout(() => {
  console.log('object');
}, 5000)
console.log('node');

위 코드의 이벤트 프로세스 정리

메인 스레드에 들어가서 setTimeout()을 실행하면 콜백 함수가 비동기 작업으로 비동기 큐 타이머 큐에 들어가고 일시적으로 실행되지 않습니다.
  • 계속해서 타이머 뒤에서 콘솔을 실행하고 "node"를 인쇄하세요.
  • 이벤트 루프가 있는지 확인하세요. 예, 폴링 주기를 거칩니다. 타이머 - 보류 중인 콜백 - 유휴 준비...
  • 부터
  • poll
  • 큐까지 반복을 중지하고 기다립니다. 이때가 5초도 안됐기 때문에 타이머 큐에는 작업이 없어 폴 큐에 갇혀 있었고, 동시에 다른 큐에 작업이 있는지 확인하기 위해 폴링합니다.
    setTimeout의 콜백이 타이머에 채워질 때까지 기다립니다. 루틴 폴링은 타이머 대기열에 작업이 있는지 확인한 다음 콜백을 확인하고 닫은 후 타이머에 도달합니다. 타이머 대기열을 지웁니다.
  • 계속해서 이벤트 루프가 필요한지 묻고 기다리면 종료됩니다.

이 문제를 이해하려면 아래 코드 및 프로세스 분석을 살펴보세요.
setTimeout(function t1() {
  console.log('setTimeout');
}, 5000)
console.log('node 生命周期');

const http = require('http')

const server = http.createServer(function h1() {
  console.log('请求回调');
});

server.listen(8080)
코드 분석은 다음과 같습니다.

평소와 마찬가지로 메인 스레드를 먼저 실행하고 "node life"를 인쇄합니다. 사이클", http를 도입한 다음 http 서비스를 생성합니다.
  • 그런 다음 이벤트 루프는 비동기 작업이 있는지 확인하고 타이머 작업과 요청 작업이 있는지 찾습니다. 따라서 이벤트 루프를 입력하십시오.
  • 6개 대기열에 작업이 없으면 폴 대기열에서 기다리세요. 아래 그림과 같이
  • 그림과 텍스트의 조합은 Nodejs의 이벤트 루프를 이해하는 데 도움이 됩니다.

    5초 후에 타이머에 작업이 있고 프로세스는 폴링부터 시작하여 콜백 확인 및 닫기 대기열을 통과한 후 이벤트 루프에 도달합니다.
  • 이벤트 루프는 비동기 작업이 있는지 확인하고 타이머 작업과 요청 작업이 있는지 찾습니다. 따라서 이벤트 루프를 다시 입력하십시오.
  • 타이머 대기열에 도착하여 콜백 함수 작업이 있음을 발견하면 콜백을 순서대로 실행하고 타이머 대기열을 지웁니다(물론 5초 후에 도착하는 콜백은 하나만 있으므로 그냥 완료하세요) 직접 실행), "setTimeout"을 인쇄합니다. 아래 그림과 같이
  • 그림과 텍스트의 조합은 Nodejs의 이벤트 루프를 이해하는 데 도움이 됩니다.

    타이머 큐를 지운 후 폴링은 폴 큐까지 계속됩니다. 이제 폴 큐는 빈 큐이므로 여기에서 기다립니다.
  • 나중에 사용자 요청이 전송된다고 가정하면 h1 콜백 함수가 폴 큐에 배치됩니다. 따라서 폴에는 실행해야 할 콜백 함수가 있으며, 폴 큐가 지워질 때까지 콜백이 순차적으로 실행됩니다.
  • 폴링 대기열이 지워졌습니다. 현재 폴링 대기열은 비어 있습니다. 계속 기다리세요.
  • 그림과 텍스트의 조합은 Nodejs의 이벤트 루프를 이해하는 데 도움이 됩니다.

  • 由于node线程一直holding在poll队列,等很长一段时间还是没有任务来临时,会自动断开等待(不自信表现),向下执行轮询流程,经过check、close callbacks后到达event loop
  • 到了event loop后,检查是否有异步任务,检查发现有请求任务。(此时定时器任务已经执行完毕,所以没有了),则继续再次进入事件循环。
  • 到达poll队列,再次holding……
  • 再等很长时间没有任务来临,自动断开到even loop(再补充一点无任务的循环情况)
  • 再次回到poll队列挂起
  • 无限循环……

梳理事件循环流程图:

注意:下图中的“是否有任务”的说法表示“是否有本队列的任务”。

1그림과 텍스트의 조합은 Nodejs의 이벤트 루프를 이해하는 데 도움이 됩니다.

event loop流程梳理

再用一个典型的例子验证下流程:

const startTime = new Date();

setTimeout(function f1() {
  console.log('setTimeout', new Date(), new Date() - startTime);
}, 200)

console.log('node 生命周期', startTime);

const fs = require('fs')

fs.readFile('./poll.js', 'utf-8', function fsFunc(err, data) {
  const fsTime = new Date()
  console.log('fs', fsTime);
  while (new Date() - fsTime < 300) {
  }
  console.log(&#39;结束死循环&#39;, new Date());
});

连续运行三遍,打印结果如下:

1그림과 텍스트의 조합은 Nodejs의 이벤트 루프를 이해하는 데 도움이 됩니다.

执行流程解析:

  • 执行全局上下文,打印「node 生命周期 + 时间」

  • 询问是否有event loop

  • 有,进入timers队列,检查没有计时器(cpu处理速度可以,这时还没到200ms)

  • 轮询进入到poll,读文件还没读完(比如此时才用了20ms),因此poll队列是空的,也没有任务回调

  • 在poll队列等待……不断轮询看有没有回调

  • 文件读完,poll队列有了fsFunc回调函数,并且被执行,输出「fs + 时间」

  • 在while死循环那里卡300毫秒,

  • 死循环卡到200ms的时候,f1回调进入timers队列。但此时poll队列很忙,占用了线程,不会向下执行。

  • 直到300ms后poll队列清空,输出「结束死循环 + 时间」

  • event loop赶紧向下走

  • 再来一轮到timers,执行timers队列里的f1回调。于是看到「setTimeout + 时间」

  • timers队列清空,回到poll队列,没有任务,等待一会。

  • 等待时间够长后,向下回到event loop。

  • event loop检查没有其他异步任务了,结束线程,整个程序over退出。

check 阶段

检查阶段(使用 setImmediate 的回调会直接进入这个队列)

check队列的实际工作原理

真正的队列,里边扔的就是待执行的回调函数的集合。类似[fn,fn]这种形式的。
每次到达check这个队列后,立即按顺序执行回调函数即可【类似于[fn1,fn2].forEach((fn)=>fn())的感觉】

所以说,setImmediate不是一个计时器的概念。

如果你去面试,涉及到Node环节,可能会遇到下边这个问题:setImmediate和setTimeout(0)谁更快。

setImmediate() 与 setTimeout(0) 的对比

  • setImmediate的回调是异步的,和setTimeout回调性质一致。
  • setImmediate回调在check队列,setTimeout回调在timers队列(概念意义,实际在计时器线程,只是setTimeout在timers队列做检查调用而已。详细看timers的工作原理)。
  • setImmediate函数调用后,回调函数会立即push到check队列,并在下次eventloop时被执行。setTimeout函数调用后,计时器线程增加一个定时器任务,下次eventloop时会在timers阶段里检查判断定时器任务是否到达时间,到了则执行回调函数。
  • 综上,setImmediate的运算速度比setTimeout(0)的要快,因为setTimeout还需要开计时器线程,并增加计算的开销。

二者的效果差不多。但是执行顺序不定

观察以下代码:

setTimeout(() => {
  console.log(&#39;setTimeout&#39;);
}, 0);

setImmediate(() => {
  console.log(&#39;setImmediate&#39;);
});

多次反复运行,执行效果如下:

1그림과 텍스트의 조합은 Nodejs의 이벤트 루프를 이해하는 데 도움이 됩니다.

顺序不定

可以看到多次运行,两句console.log打印的顺序不定。
这是因为setTimeout的间隔数最小填1,虽然下边代码填了0。但实际计算机执行当1ms算。(这里注意和浏览器的计时器区分。在浏览器中,setInterval的最小间隔数为10ms,小于10ms则会被设置为10;设备供电状态下,间隔最小为16.6ms。)

以上代码,主线程运行的时候,setTimeout函数调用,计时器线程增加一个定时器任务。setImmediate函数调用后,其回调函数立即push到check队列。主线程执行完毕。

eventloop判断时,发现timers和check队列有内容,进入异步轮询:

第一种情况:等到了timers里这段时间,可能还没有1ms的时间,定时器任务间隔时间的条件不成立所以timers里还没有回调函数。继续向下到了check队列里,这时候setImmediate的回调函数早已等候多时,直接执行。而再下次eventloop到达timers队列,定时器也早已成熟,才会执行setTimeout的回调任务。于是顺序就是「setImmediate -> setTimeout」。

第二种情况:但也有可能到了timers阶段时,超过了1ms。于是计算定时器条件成立,setTimeout的回调函数被直接执行。eventloop再向下到达check队列执行setImmediate的回调。最终顺序就是「setTimeout -> setImmediate」了。

所以,只比较这两个函数的情况下,二者的执行顺序最终结果取决于当下计算机的运行环境以及运行速度。

二者时间差距的对比代码

------------------setTimeout测试:-------------------
let i = 0;
console.time(&#39;setTimeout&#39;);
function test() {
  if (i < 1000) {
    setTimeout(test, 0)
    i++
  } else {
    console.timeEnd(&#39;setTimeout&#39;);
  }
}
test();

------------------setImmediate测试:-------------------
let i = 0;
console.time(&#39;setImmediate&#39;);
function test() {
  if (i < 1000) {
    setImmediate(test)
    i++
  } else {
    console.timeEnd(&#39;setImmediate&#39;);
  }
}
test();

运行观察时间差距:

1그림과 텍스트의 조합은 Nodejs의 이벤트 루프를 이해하는 데 도움이 됩니다.

setTimeout与setImmediate时间差距

可见setTimeout远比setImmediate耗时多得多
这是因为setTimeout不仅有主代码执行的时间消耗。还有在timers队列里,对于计时器线程中各个定时任务的计算时间。

结合poll队列的面试题(考察timers、poll和check的执行顺序)

如果你看懂了上边的事件循环图,下边这道题难不倒你!

// 说说下边代码的执行顺序,先打印哪个?
const fs = require(&#39;fs&#39;)
fs.readFile(&#39;./poll.js&#39;, () => {
  setTimeout(() => console.log(&#39;setTimeout&#39;), 0)
  setImmediate(() => console.log(&#39;setImmediate&#39;))
})

上边这种代码逻辑,不管执行多少次,肯定都是先执行setImmediate。

1그림과 텍스트의 조합은 Nodejs의 이벤트 루프를 이해하는 데 도움이 됩니다.

先执行setImmediate

因为fs各个函数的回调是放在poll队列的。当程序holding在poll队列后,出现回调立即执行。
回调内执行setTimeout和setImmediate的函数后,check队列立即增加了回调。
回调执行完毕,轮询检查其他队列有内容,程序结束poll队列的holding向下执行。
check是poll阶段的紧接着的下一个。所以在向下的过程中,先执行check阶段内的回调,也就是先打印setImmediate。
到下一轮循环,到达timers队列,检查setTimeout计时器符合条件,则定时器回调被执行。

nextTick 与 Promise

说完宏任务,接下来说下微任务

  • 二者都是「微队列」,执行异步微任务。
  • 二者不是事件循环的一部分,程序也不会开启额外的线程去处理相关任务。(理解:promise里发网络请求,那是网络请求开的网络线程,跟Promise这个微任务没关系)
  • 微队列设立的目的就是让一些任务「马上」、「立即」优先执行。
  • nextTick与Promise比较,nextTick的级别更高。

nextTick表现形式

process.nextTick(() => {})

Promise表现形式

Promise.resolve().then(() => {})

如何参与事件循环?

事件循环中,每执行一个回调前,先按序清空一次nextTick和promise。

// 先思考下列代码的执行顺序
setImmediate(() => {
  console.log(&#39;setImmediate&#39;);
});

process.nextTick(() => {
  console.log(&#39;nextTick 1&#39;);
  process.nextTick(() => {
    console.log(&#39;nextTick 2&#39;);
  })
})

console.log(&#39;global&#39;);


Promise.resolve().then(() => {
  console.log(&#39;promise 1&#39;);
  process.nextTick(() => {
    console.log(&#39;nextTick in promise&#39;);
  })
})

最终顺序:

  • global

  • nextTick 1

  • nextTick 2

  • promise 1

  • nextTick in promise

  • setImmediate

两个问题:

基于上边的说法,有两个问题待思考和解决:

  • 每走一个异步宏任务队列就查一遍nextTick和promise?还是每执行完 宏任务队列里的一个回调函数就查一遍呢?

  • 如果在poll的holding阶段,插入一个nextTick或者Promise的回调,会立即停止poll队列的holding去执行回调吗?

그림과 텍스트의 조합은 Nodejs의 이벤트 루프를 이해하는 데 도움이 됩니다.

上边两个问题,看下边代码的说法

setTimeout(() => {
  console.log(&#39;setTimeout 100&#39;);
  setTimeout(() => {
    console.log(&#39;setTimeout 100 - 0&#39;);
    process.nextTick(() => {
      console.log(&#39;nextTick in setTimeout 100 - 0&#39;);
    })
  }, 0)
  setImmediate(() => {
    console.log(&#39;setImmediate in setTimeout 100&#39;);
    process.nextTick(() => {
      console.log(&#39;nextTick in setImmediate in setTimeout 100&#39;);
    })
  });
  process.nextTick(() => {
    console.log(&#39;nextTick in setTimeout100&#39;);
  })
  Promise.resolve().then(() => {
    console.log(&#39;promise in setTimeout100&#39;);
  })
}, 100)

const fs = require(&#39;fs&#39;)
fs.readFile(&#39;./1.poll.js&#39;, () => {
  console.log(&#39;poll 1&#39;);
  process.nextTick(() => {
    console.log(&#39;nextTick in poll ======&#39;);
  })
})

setTimeout(() => {
  console.log(&#39;setTimeout 0&#39;);
  process.nextTick(() => {
    console.log(&#39;nextTick in setTimeout&#39;);
  })
}, 0)

setTimeout(() => {
  console.log(&#39;setTimeout 1&#39;);
  Promise.resolve().then(() => {
    console.log(&#39;promise in setTimeout1&#39;);
  })
  process.nextTick(() => {
    console.log(&#39;nextTick in setTimeout1&#39;);
  })
}, 1)

setImmediate(() => {
  console.log(&#39;setImmediate&#39;);
  process.nextTick(() => {
    console.log(&#39;nextTick in setImmediate&#39;);
  })
});

process.nextTick(() => {
  console.log(&#39;nextTick 1&#39;);
  process.nextTick(() => {
    console.log(&#39;nextTick 2&#39;);
  })
})

console.log(&#39;global ------&#39;);

Promise.resolve().then(() => {
  console.log(&#39;promise 1&#39;);
  process.nextTick(() => {
    console.log(&#39;nextTick in promise&#39;);
  })
})

/** 执行顺序如下
global ------
nextTick 1
nextTick 2
promise 1
nextTick in promise
setTimeout 0 // 解释问题1. 没有上边的nextTick和promise,setTimeout和setImmediate的顺序不一定,有了以后肯定是0先开始。
// 可见,执行一个队列之前,就先检查并执行了nextTick和promise微队列
nextTick in setTimeout
setTimeout 1
nextTick in setTimeout1
promise in setTimeout1
setImmediate
nextTick in setImmediate
poll 1
nextTick in poll ======
setTimeout 100
nextTick in setTimeout100
promise in setTimeout100
setImmediate in setTimeout 100
nextTick in setImmediate in setTimeout 100
setTimeout 100 - 0
nextTick in setTimeout 100 - 0
 */

以上代码执行多次,顺序不变,setTimeout和setImmediate的顺序都没变。

执行顺序及具体原因说明如下:

  • global :主线程同步任务,率先执行没毛病

  • nextTick 1:执行异步宏任务之前,清空异步微任务,nextTick优先级高,先行一步

  • nextTick 2:执行完上边这句代码,又一个nextTick微任务,立即率先执行

  • promise 1:执行异步宏任务之前,清空异步微任务,Promise的优先级低,所以在nextTick完了以后立即执行

  • nextTick in promise:清空Promise队列的过程中,遇到nextTick微任务,立即执行、清空

  • setTimeout 0: 解释第一个问题. 没有上边的nextTick和promise,只有setTimeout和setImmediate时他俩的执行顺序不一定。有了以后肯定是0先开始。可见,执行一个宏队列之前,就先按顺序检查并执行了nextTick和promise微队列。等微队列全部执行完毕,setTimeout(0)的时机也成熟了,就被执行。

  • nextTick in setTimeout:执行完上边这句代码,又一个nextTick微任务,立即率先执行 【这种回调函数里的微任务,我不能确定是紧随同步任务执行的;还是放到微任务队列,等下一个宏任务执行前再清空的他们。但是顺序看上去和立即执行他们一样。不过我比较倾向于是后者:先放到微任务队列等待,下一个宏任务执行前清空他们。】

  • setTimeout 1:因为执行微任务耗费时间,导致此时timers里判断两个0和1的setTimeout计时器已经结束,所以两个setTimeout回调都已加入队列并被执行

  • nextTick in setTimeout1:执行完上边这句代码,又一个nextTick微任务,立即率先执行 【可能是下一个宏任务前清空微任务】

  • promise in setTimeout1:执行完上边这句代码,又一个Promise微任务,立即紧随执行 【可能是下一个宏任务前清空微任务】

  • setImmediate:poll队列回调时机未到,先行向下到check队列,清空队列,立即执行setImmediate回调

  • nextTick in setImmediate:执行完上边这句代码,又一个nextTick微任务,立即率先执行 【可能是下一个宏任务前清空微任务】

  • poll 1:poll队列实际成熟,回调触发,同步任务执行。

  • nextTick in poll :执行完上边这句代码,又一个nextTick微任务,立即率先执行 【可能是下一个宏任务前清空微任务】

  • setTimeout 100:定时器任务到达时间,执行回调。并在回调里往微任务推入了nextTick、Promise,往宏任务的check里推入了setImmediate的回调。并且也开启了计时器线程,往timers里增加了下一轮回调的可能。

  • nextTick in setTimeout100:宏任务向下前,率先执行定时器回调内新增的微任务-nextTick 【这里就能确定了,是下一个宏任务前清空微任务的流程】

  • promise in setTimeout100:紧接着执行定时器回调内新增的微任务-Promise 【清空完nextTick清空Promise的顺序】

  • setImmediate in setTimeout 100:这次setImmediate比setTimeout(0)先执行的原因是:流程从timers向后走到check队列,已经有了setImmediate的回调,立即执行。

  • nextTick in setImmediate in setTimeout 100:执行完上边这句代码,又一个nextTick微任务,下一个宏任务前率先清空微任务

  • setTimeout 100 - 0:轮询又一次回到timers,执行100-0的回调。

  • nextTick in setTimeout 100 - 0:执行完上边这句代码,又一个nextTick微任务,下一个宏任务前率先清空微任务。

扩展:为什么有了setImmediate还要有nextTick和Promise?

一开始设计的时候,setImmediate充当了微队列的作用(虽然他不是)。设计者希望执行完poll后立即执行setImmediate(当然现在也确实是这么表现的)。所以起的名字叫Immediate,表示立即的意思。 但是后来问题是,poll里可能有N个任务连续执行,在执行期间想要执行setImmediate是不可能的。因为poll队列不停,流程不向下执行。

于是出现nextTick,真正的微队列概念。但此时,immediate的名字被占用了,所以名字叫nextTick(下一瞬间)。事件循环期间,执行任何一个队列之前,都要检查他是否被清空。其次是Promise。

面试题

最后,检验学习成果的面试题来了

async function async1() {
  console.log(&#39;async start&#39;);
  await async2();
  console.log(&#39;async end&#39;);
}

async function async2(){
  console.log(&#39;async2&#39;);
}
console.log(&#39;script start&#39;);

setTimeout(() => {
  console.log(&#39;setTimeout 0&#39;);
}, 0)

setTimeout(() => {
  console.log(&#39;setTimeout 3&#39;);
}, 3)

setImmediate(() => {
  console.log(&#39;setImmediate&#39;);
})

process.nextTick(() => {
  console.log(&#39;nextTick&#39;);
})

async1();

new Promise((res) => {
  console.log(&#39;promise1&#39;);
  res();
  console.log(&#39;promise2&#39;);
}).then(() => {
  console.log(&#39;promise 3&#39;);
});

console.log(&#39;script end&#39;);

// 答案如下
// -
// -
// -
// -
// -
// -
// -
// -
// -
// -
// -
// -






/**
script start
async start
async2
promise1
promise2
script end

nextTick
async end
promise 3

// 后边这仨的运行顺序就是验证你电脑运算速度的时候了。
速度最好(执行上边的同步代码 + 微任务 + 计时器运算用了不到0ms):
setImmediate
setTimeout 0
setTimeout 3

速度中等(执行上边的同步代码 + 微任务 + 计时器运算用了0~3ms以上):
setTimeout 0
setImmediate
setTimeout 3

速度较差(执行上边的同步代码 + 微任务 + 计时器运算用了3ms以上):
setTimeout 0
setTimeout 3
setImmediate
*/

思维脑图 - Node生命周期核心阶段

1그림과 텍스트의 조합은 Nodejs의 이벤트 루프를 이해하는 데 도움이 됩니다.

그림과 텍스트의 조합은 Nodejs의 이벤트 루프를 이해하는 데 도움이 됩니다.

更多编程相关知识,请访问:编程视频!!

위 내용은 그림과 텍스트의 조합은 Nodejs의 이벤트 루프를 이해하는 데 도움이 됩니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

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