开发者社区> 问答> 正文

Node 中的 Event Loop 和浏览器中的有什么区别?

Node 中的 Event Loop 和浏览器中的有什么区别?process.nexttick 执行顺序?


【精品问答】前端面试手册

【精品问答】前端面试手册之Node篇

展开
收起
前端问答 2019-12-23 12:09:11 747 0
1 条回答
写回答
取消 提交回答
  • 前端问答小助手

    Node 中的 Event Loop 和浏览器中的是完全不相同的东西。

    Node 的 Event Loop 分为 6 个阶段,它们会按照顺序反复运行。每当进入某一个阶段的时候,都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量到达系统设定的阈值,就会进入下一阶段。

    image.png

    timer

    timers 阶段会执行 setTimeout 和 setInterval 回调,并且是由 poll 阶段控制的。

    同样,在 Node 中定时器指定的时间也不是准确时间,只能是尽快执行。

    I/O

    I/O 阶段会处理一些上一轮循环中的少数未执行的 I/O 回调

    idle, prepare

    idle, prepare 阶段内部实现,这里就忽略不讲了。

    poll

    poll 是一个至关重要的阶段,这一阶段中,系统会做两件事情

    1. 回到 timer 阶段执行回调
    2. 执行 I/O 回调

    并且在进入该阶段时如果没有设定了 timer 的话,会发生以下两件事情

    • 如果 poll 队列不为空,会遍历回调队列并同步执行,直到队列为空或者达到系统限制
    • 如果 poll 队列为空时,会有两件事发生
      • 如果有 setImmediate 回调需要执行,poll 阶段会停止并且进入到 check 阶段执行回调
      • 如果没有 setImmediate 回调需要执行,会等待回调被加入到队列中并立即执行回调,这里同样会有个超时时间设置防止一直等待下去

    当然设定了 timer 的话且 poll 队列为空,则会判断是否有 timer 超时,如果有的话会回到 timer 阶段执行回调。

    check

    check 阶段执行 setImmediate

    close callbacks

    close callbacks 阶段执行 close 事件

    在以上的内容中,我们了解了 Node 中的 Event Loop 的执行顺序,接下来我们将会通过代码的方式来深入理解这块内容。

    首先在有些情况下,定时器的执行顺序其实是随机的

    setTimeout(() => {
        console.log('setTimeout')
    }, 0)
    setImmediate(() => {
        console.log('setImmediate')
    })
    
    

    对于以上代码来说,setTimeout 可能执行在前,也可能执行在后

    • 首先 setTimeout(fn, 0) === setTimeout(fn, 1),这是由源码决定的
    • 进入事件循环也是需要成本的,如果在准备时候花费了大于 1ms 的时间,那么在 timer 阶段就会直接执行 setTimeout 回调
    • 那么如果准备时间花费小于 1ms,那么就是 setImmediate 回调先执行了

    当然在某些情况下,他们的执行顺序一定是固定的,比如以下代码:

    const fs = require('fs')
    
    fs.readFile(__filename, () => {
        setTimeout(() => {
            console.log('timeout');
        }, 0)
        setImmediate(() => {
            console.log('immediate')
        })
    })
    
    

    在上述代码中,setImmediate 永远先执行。因为两个代码写在 IO 回调中,IO 回调是在 poll 阶段执行,当回调执行完毕后队列为空,发现存在 setImmediate 回调,所以就直接跳转到 check 阶段去执行回调了。

    上面介绍的都是 macrotask 的执行情况,对于 microtask 来说,它会在以上每个阶段完成前清空 microtask 队列,下图中的 Tick 就代表了 microtask

    image.png

    setTimeout(() => {
      console.log('timer21')
    }, 0)
    
    Promise.resolve().then(function() {
      console.log('promise1')
    })
    
    

    对于以上代码来说,其实和浏览器中的输出是一样的,microtask 永远执行在 macrotask 前面。

    最后我们来讲讲 Node 中的 process.nextTick,这个函数其实是独立于 Event Loop 之外的,它有一个自己的队列,当每个阶段完成后,如果存在 nextTick 队列,就会清空队列中的所有回调函数,并且优先于其他 microtask 执行。

    setTimeout(() => {
     console.log('timer1')
    
     Promise.resolve().then(function() {
       console.log('promise1')
     })
    }, 0)
    
    process.nextTick(() => {
     console.log('nextTick')
     process.nextTick(() => {
       console.log('nextTick')
       process.nextTick(() => {
         console.log('nextTick')
         process.nextTick(() => {
           console.log('nextTick')
         })
       })
     })
    })
    
    

    对于以上代码,大家可以发现无论如何,永远都是先把 nextTick 全部打印出来。

    2019-12-23 12:12:24
    赞同 1 展开评论 打赏
问答排行榜
最热
最新

相关电子书

更多
Egg— 企业级 Node 框架 立即下载
探究 Node.js 的服务端之路 立即下载
基于浏览器的实时构建探索之路--玄寂 立即下载