nodejs中的轮询机制

2,512 阅读3分钟

node简介

  • node.js是一个基于ChromeV8引擎的js运行环境
  • node.js使用commonjs模块化规范
  • 在node中,每一个模块就是一个js文件,每个模块都被一个函数包裹,但这个函数被隐藏了,这个时候可以用 arguments 来找到它 在node中输入console.log(arguments.callee.toString()); 就可以拿到包裹函数的实参
    被隐藏的包裹函数
    这5个参数代表
    exports 暴露
    require 引入
    module 暴露
    __filename 当前模块文件的绝对路径
    __dirname  当前模块文件的相对路径
  • 在node中是不存在Dom的,绝大部分Bom也不存在,node中不存在window对象,它通过global来取代了window

轮询机制

言归正传,node的事件轮询机制并不同于浏览器的事件轮询机制,具体如下

  1. 定时器:
    本阶段执行已经安排的 setTimeout() 和 setInterval() 的回调函数。
  2. 待定回调:
    执行延迟到下一个循环迭代的 I/O 回调。
    TCP错误回调函数
  3. idle, prepare:
    仅系统内部使用。
  4. 轮询:
    轮询回调队列:需要将来执行的回调函数
    轮询轮询回调队列,看是否由回调函数要执行
      - 轮询回调队列有内容
        直到轮询回调队列为空
      - 轮询回调队列没有内容
        如果之前设置过setImmediate函数,就去下一个阶段
        如果没有设置过,就在当前阶段等待(等待新的回调函数被添加进来)
        特殊:如果定时器时间到了,也会去下一个阶段
  5. 检测:
    setImmediate() 回调函数在这里执行。
  6. 关闭的回调函数:
    执行关闭事件的回调函数 close end 

这里要说明一下,事件轮询是无限的死循环,到了最后一步后又会返回第一步, 还有就是process.nextTick函数会在任意阶段,优先执行。先看一段简单的代码

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

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

process.nextTick(() => {
    console.log(3);
});

console.log(4);

毋庸置疑,最先输出的肯定是4,然后这段代码的回调队列为 [setTimeout, setImmediate, process.nextTick], 因为process.nextTick会优先执行,所以接下来输出3,此时回调队列为 [setTimeout, setImmediate], 依次取出,同步执行,输出1和2。 最终结果为 4,3,1,2

在来看一段稍微复杂一点的

setTimeout(() => {      //setTimeout1
    console.log(1);

    setTimeout(() => {      //setTimeout2
        console.log(2);
    }, 0);

    setImmediate(() => {        //setImmediate2
        console.log(3);
    });

    process.nextTick(() => {        //process2
        console.log(4);
    });
}, 0);

setImmediate(() => {        //setImmediate1
    console.log(5);
});

process.nextTick(() => {        //process1
    console.log(6);
});

console.log(7);

先一点点解析,代码解析完后,此时的回调队列应为 【setTimeout1, setImmediate1, process1】,因为此时setTimeout1的回调函数并没有执行,所以里面的函数没有添加到回调队列,然后process1会插队,所以前两个输出 7,6, 接下来开始执行setTimeout1,此时会接着输出1,然后其他函数会添加到回调队列,这个时候回调队列应为【setImmediate1, setTimeout2, setImmediate2, process2】,process2优先输出,最终结果为 7,6,1,4,5,3,2

这里可能有人会不懂为什么先输出3,而不是先输出2,这里回到事件轮询机制的第一步,执行完setTimeout1之后,setImmediate2已被添加到第5步的队列中,所以先输出3,执行完第6步之后,在开始下一次事件轮询的时候执行setTimeout2

再来最后一段代码

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

Promise.resolve() 
    .then(() => {
        console.log(2);

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

        process.nextTick(() => {
            console.log(4);
        })
    })
    .then(() => {
        console.log(5);
    })
    
process.nextTick(() => {
    console.log(6);
})

console.log(7);

输出结果为7,6,2,5,4,1,3 这里简单说一下宏任务与微任务

宏任务

浏览器 Node
setTimeout
setInterval
setImmediate x
requestAnimationFrame x

微任务

浏览器 Node
process.nextTick x
MutationObserver x
Promise.then catch finally

宏任务与微任务之间的关系图

也就是说在执行异步任务时,先看有没有微任务,没有的话在执行宏任务,然后再看有没有微任务,周而复始。
要给微任务一个优先级的话,就是同一层级的优先执行,下一层级后执行,如上面这段代码所示,先输出5,后输出4。