事件循环就是主线程何时去执行异步队列。
node将事件循环分成六个队列,它们分别是:

  1. timmer (用于存储定时器的回调函数 setlnterval,setTimeout)
  2. pending callbacks (操作系统相关的函数回调,比如启动服务器端口监听的回调)
  3. idle,prepare (系统使用的)
  4. poll (I/O操作相关的回调,比如文件读写的回调函数)
  5. check (setlmmediate的回调)
  6. close callbaks (一些关闭事件的回调,比如数据库连接断开)

这六个阶段都存储了,异步回调,每次事件轮训,就按照这六个步骤顺序轮训。

nodejs中的异步也分为微任务队列(如:promise.then)和宏任务队列(如:setTimeout),微任务的优先级高于宏任务。

  • 每次事件轮训完会先去检查微任务队列,微任务队列中有事件,就执行完微任务,然后再去执行宏任务。
  • node中的微任务是穿插在上面的六个队列执行中的,每执行完一个宏任务就去检查微任务队列中是否有任务,有就优先清空微任务队列,然后再进行下一个步骤。

我们先看一个例子

new Promise((resolve, reject) => {
  resolve();
}).then(() => {
  console.log('hello world promise');
});


setTimeout(() => {
  console.log('hello world setTimeout');
})

new Promise((resolve, reject) => {
  resolve();
}).then(() => {
  console.log('hello world setTimeout after promise1');
});

new Promise((resolve, reject) => {
  resolve();
}).then(() => {
  console.log('hello world setTimeout after promise2');
});

setImmediate(() => {
  console.log('hello world setImmediate');
});

new Promise((resolve, reject) => {
  resolve();
}).then(() => {
  console.log('hello world setImmediate after promise1');
});

上面的代码在node中的输出结果

hello world promise
hello world setTimeout after promise1
hello world setTimeout after promise2
hello world setImmediate after promise1
hello world setTimeout
hello world setImmediate

首先,上面的例子中node一启动应用,就开始了事件循环,在事件循环中,有一个timmer回调一个setImmediate回调,然后三个promise微任务,按照node的运行过程,先执行微任务在执行宏任务,所以promise先被清空,然后按照;六个队列顺序执行。timmer在setImmediate队列之前所以先执行timmer。

注意:我运行了多次,中间总会出现一两次setImmediate在timmer之前执行。这种情况查询,是因为应用环境启动就进入事件轮训,timmer可能还没有注册好,那么就只能等待下一次事件轮训所以它在setImmediate之后,但是这并不代表违反了 上面的队列运行顺序,队列的轮训还是按照上面的顺序来的。只是timmer是在下一次的轮训中被执行的所以 看起来它是后执行的

我们将上面的例子改一下来说明一下微任务在宏任务队列中的穿插情况:

setTimeout(() => {
  console.log('hello world setTimeout1');
  new Promise((resolve, reject) => {
    resolve();
  }).then(() => {
    console.log('hello world setTimeout1 promise');
  });
})

setTimeout(() => {
  console.log('hello world setTimeout2');
  new Promise((resolve, reject) => {
    resolve();
  }).then(() => {
    console.log('hello world setTimeout2 promise');
  });
})

new Promise((resolve, reject) => {
  resolve();
}).then(() => {
  console.log('hello world setTimeout after promise');
});


setImmediate(() => {
  console.log('hello world setImmediate');
  new Promise((resolve, reject) => {
    resolve();
  }).then(() => {
    console.log('hello world setImmediate promise');
  });
});

输出结果:

hello world setTimeout after promise
hello world setTimeout1
hello world setTimeout1 promise
hello world setTimeout2
hello world setTimeout2 promise
hello world setImmediate
hello world setImmediate promise

我们按照事件轮训机制来看执行逻辑。

  1. 首先应用开始进入事件轮训,此时 轮训队列中有第一个timmer队列和第五个check队列(setImmediate),以及微任务peomise 队列,按照微任务优先于宏任务的过程。所以先执行微任务于是输出 hello world setTimeout after promise,微任务队列清空。
  2. 微任务清空了,然后按照事件循环的六个队列顺序开始执行timmer队列,于是开始执行timmer队列,按照顺序输出hello world setTimeout1,并且在timmer队列第一个任务中中产生一个微任务。于是微任务放入微任务队列。当第一个宏任务timer执行完就又去检查微任务队列,微任务队列中有任务于是清空微任务队列。然后又回到宏任务队列开始执行第二个timer,第二个timer执行完,又去检查微任务队列。
  3. timer队列执行完毕按照顺序 往下检查队列,直到check队列中有setImmediate,开始执行check队列宏任务输出hello world setImmediate,然后宏任务中又产生一个微任务,执行完check队列的一个宏任务就开始检查 微任务队列,有一个刚刚setImmediate回调中产生的promise微任务,于是输出hello world setImmediate promise

所以总结一下:

  1. 在node中同步任务优先于事件循环,也就是同步执行完才会开始事件循环
  2. 事件循环就分为六个阶段总是按照六个阶段的顺序执行
  3. 微任务优先于宏任务,微任务在事件循环中穿插执行,每执行完一次宏任务就会去检查微任务队列,并且清空微任务

H_H_code
51 声望3 粉丝