Nodejs中的EventLoop机制

67 阅读3分钟

概述

和浏览器中一样NodeJS中也有事件环(Event Loop),但是由于执行代码的宿主环境和应用场景不同,所以两者的事件环也有所不同。

NodeJS事件环和浏览器事件环区别

  1. 任务队列个数不同

    • 浏览器事件环有2个事件队列(宏任务队列和微任务队列)
    • NodeJS事件环有6个事件队列
  2. 微任务队列不同

    • 浏览器事件环中有专门存储微任务的队列
    • NodeJS事件环中没有专门存储微任务的队列
  3. 微任务执行时机不同

    • 浏览器事件环中每执行完一个宏任务都会去清空微任务队列
    • NodeJS事件环中只有同步代码执行完毕和其它队列之间切换的时候会去清空微任务队列
  4. 微任务优先级不同

    • 浏览器事件环中如果多个微任务同时满足执行条件, 采用先进先出
    • NodeJS事件环中如果多个微任务同时满足执行条件, 会按照优先级执行

NodeJS中的任务队列

nodeEventLoop.png

  1. 注意点:

    和浏览器不同的是没有宏任务队列和微任务队列的概念 宏任务被放到了不同的队列中, 但是没有队列是存放微任务的队列 微任务会在执行完同步代码和队列切换的时候执行

    什么时候切换队列?

    • 当队列为空(已经执行完毕或者没有满足条件回到)
    • 或者执行的回调函数数量到达系统设定的阈值时任务队列就会切换
  2. 注意点:

    在NodeJS中process.nextTick微任务的优先级高于Promise.resolve微任务

     Promise.resolve().then(function () {
         console.log("Promise");
     });
     process.nextTick(function () {
         console.log("process.nextTick");
     });
     // 依次输出:process.nextTick、Promise
    
  3. 执行过程分析: nodeEventLoop2.png

这里举个例子来说明一下:

setTimeout(function () {
    console.log("setTimeout1");
    // p1
    Promise.resolve().then(function () {
        console.log("Promise1");
    });
    // n1
    process.nextTick(function () {
        console.log("process.nextTick1");
    });
});
console.log("同步代码 Start");
setTimeout(function () {
    console.log("setTimeout2");
    // p2
    Promise.resolve().then(function () {
        console.log("Promise2");
    });
    // n2
    process.nextTick(function () {
        console.log("process.nextTick2");
    });
});
console.log("同步代码 End");
/*
    同步代码 Start
    同步代码 End
    setTimeout1 
    process.nextTick1
    Promise1
    setTimeout2
    process.nextTick2
    Promise2
*/

过程分析:

  1. 先执行 setTimeout1,将他先放入 timers 队列,继续向下执行。
  2. 执行到同步代码,直接输出: ‘同步代码 Start’
  3. 再执行到 setTimeout2, 还是将他放入 timers 队列,继续向下执行
  4. 然后执行到同步代码,输出:‘同步代码 End’
  5. 然后找符合要求的 微任务,目前没有,接着进入下一轮循环。
  6. 再次进入到 ‘timers’ 队列,取 timers 中的 setTimeout1 执行;输出:“setTimeout1”; 然后执行到微任务的时候,放入搁置的微任务队列(注意node中是没有微任务队列的,所以只是暂时的搁置);将“setTimeout1”从‘timers’移除。
  7. 然后执行‘timers’中的 setTimeout2,接下来和 6 步骤相同操作。
  8. 这时候‘times’队列已经全部执行完毕。然后这时候去执行微任务。此时依次输出:“process.nextTick1”,“Promise1”,“process.nextTick2”, “Promise2”。
  9. 执行完timers队列。

注意点:

  1. 此时执行完timers,会查看check队列是否有内容, 有就切换到check
  2. 如果check队列没有内容, 就会查看timers是否有内容, 有就切换到timers
  3. 如果check队列和timers队列都没有内容, 为了避免资源浪费就会阻塞在poll

面试题

以下代码的输出结果是什么?

setTimeout(function () {
    console.log("setTimeout");
}, 0);
setImmediate(function () {
    console.log("setImmediate");
});

上面的代码输出结果是随机的。在NodeJS中指定的延迟时间是有一定的误差的, 所以导致了输出结果随机的问题。

那么下面的代码运行结果又是什么呢?------> 这里先不公布答案,欢迎各位留言回答!

const path = require("path");
const fs = require("fs");

fs.readFile(path.join(__dirname, "04.js"), function () {
    setTimeout(function () {
        console.log("setTimeout");
    }, 0);
    setImmediate(function () {
        console.log("setImmediate");
    });
});