Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

面试官:说说对Nodejs中的事件循环机制理解? #159

Open
linwu-hi opened this issue Jul 30, 2023 · 0 comments
Open

面试官:说说对Nodejs中的事件循环机制理解? #159

linwu-hi opened this issue Jul 30, 2023 · 0 comments
Labels

Comments

@linwu-hi
Copy link
Owner

linwu-hi commented Jul 30, 2023

面试官:说说对Nodejs中的事件循环机制理解?

image

一、什么是事件循环?

image

在Node.js中,事件循环是基于libuv实现的。libuv是一个多平台的专注于异步IO的库,负责处理底层的事件循环和IO操作,使得Node.js能够实现非阻塞的IO。

事件循环主要是为了处理异步操作。在Node.js中,几乎所有的IO操作都是异步的,包括文件读写、网络请求等。为了能够在异步操作完成后及时执行相应的回调函数,Node.js引入了事件循环机制。

二、事件循环的流程

Node.js的事件循环是基于事件触发器的概念。事件触发器可以添加监听器,当特定事件发生时,触发相应的回调函数。事件循环不断地检查事件触发器是否有待处理的事件,如果有,则执行相应的回调函数。

事件循环的流程可以简化为以下几个步骤:

  1. 执行当前的同步代码。
  2. 检查是否有待处理的微任务(即process.nextTick和Promise的then回调),如果有,则依次执行微任务。
  3. 检查是否有待处理的宏任务(即定时器、IO事件和setImmediate回调),如果有,则执行宏任务队列中最早的一个任务。
  4. 重复上述步骤,直到所有的任务队列为空。

三、事件循环的阶段

Node.js的事件循环分为6个阶段,每个阶段都有对应的任务队列。事件循环每次遍历一个阶段,都会依次执行该阶段的任务队列中的任务。下面是Node.js事件循环的6个阶段:

  1. timers阶段:执行定时器回调。
  2. I/O事件回调阶段:执行I/O相关的回调,例如网络请求、文件读写等。
  3. idle, prepare阶段:内部使用,不常用。
  4. poll阶段:检索新的I/O事件并执行相关回调。
  5. check阶段:执行setImmediate回调。
  6. close阶段:执行关闭事件的回调。

四、题目分析

题目代码如下:

async function async1() {
    console.log('async1 start')
    await async2()
    console.log('async1 end')
}

async function async2() {
    console.log('async2')
}

console.log('script start')

setTimeout(function () {
    console.log('setTimeout0')
}, 0)

setTimeout(function () {
    console.log('setTimeout2')
}, 300)

setImmediate(() => console.log('setImmediate'));

process.nextTick(() => console.log('nextTick1'));

async1();

process.nextTick(() => console.log('nextTick2'));

new Promise(function (resolve) {
    console.log('promise1')
    resolve();
    console.log('promise2')
}).then(function () {
    console.log('promise3')
})

console.log('script end')

分析过程如下:

  1. 执行同步代码,输出script start
  2. 遇到第一个setTimeout,将其回调函数放入timer阶段。
  3. 遇到第二个setTimeout,设置了300ms后放入timer阶段。
  4. 遇到setImmediate,放入check阶段。
  5. 遇到第一个process.nextTick,放入微任务队列。
  6. 执行async1(),输出async1 start,然后执行async2(),输出async2async2后面的代码进入微任务队列,等待下一轮事件循环。
  7. 遇到第二个process.nextTick,放入微任务队列。
  8. 遇到new Promise,输出promise1,然后执行立即执行函数,输出promise2,接着then的回调进入微任务队列。
  9. 执行console.log('script end'),输出script end
  10. 执行微任务队列中的回调,依次输出nextTick1nextTick2async1 endpromise3
  11. 执行timer阶段,输出setTimeout0
  12. 执行check阶段,输出setImmediate
  13. 300ms后,timer阶段有任务,输出setTimeout2

最终输出结果为:

script start
async1 start
async2
promise1
promise2
script end
nextTick1
nextTick2
async1 end
promise3
setTimeout0
setImmediate
setTimeout2

五、setTimeout与setImmediate的输出顺序

最后还有一个关于setTimeoutsetImmediate的输出顺序的问题。

setTimeout(() => {
  console.log("setTimeout");
}, 0);

setImmediate(() => {
  console.log("setImmediate");
});

可能输出情况有两种:

情况一

setTimeout
setImmediate

情况二:

setImmediate
setTimeout

这是因为setTimeout的回调函数虽然设置了0毫秒的延迟,但是实际上会被强制改成1ms,而setImmediate会在检查阶段(check阶段)立即执行。因此,如果同步代码执行时间较长,可能会导致setTimeout的回调在setImmediate之后执行。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant