# 事件循环

https://www.imooc.com/article/27083?block_id=tuijian_wz

https://zhuanlan.zhihu.com/p/34182184

https://zhuanlan.zhihu.com/p/54882306

https://zhuanlan.zhihu.com/p/33058983

setimmediate 将任务注册到下一个check阶段,而不是当前的check阶段

const promise = Promise.resolve()
  .then(() => {
    return promise
  })
promise.catch(console.error)

// 会导致死循环

9 5 8 1 4 7 3 6 2

执行一个宏任务执行一对微任务,而不是执行完一个阶段再执行微任务?

# Node事件循环

nodejs event loop workflow

# 执行流程概览

  1. 执行脚本,初始化事件循环
  2. 开始执行事件循环:
    • Timers: setTimeout, setInterval (比较当前时间和注册时间,执行所有过期的任务)
    • Pending IO: 其他的IO回调
    • Idle,prepare: 忽略
    • Poll:文件IO、服务器IO等
    • Check:setImmediate (执行所有当前Tick下的任务)
    • Close: on('close', fn)
  3. 下一个事件循环

# 宏任务的分类

setTimeout, setInterval

setImmediate

IO 回调

# 微任务的分类

process.nextTick

Promise.then

# 微任务的执行时机

  1. 当一个宏任务执行完毕后立即检查微任务队列,并执行所有微任务(没有限制)
  2. 微任务的执行顺序:先执行process.nextTick任务,再执行promise任务

# setImmediate特性

  1. setImmediate相比于setTimeout而言少了一个时间比较的过程,执行更快(性能快出50倍左右,可以说是出于性能考虑使用setImmediate替代setTimeout(fn, 0))。

  2. setImmediate置于Poll阶段之后,对于IO回调可以尽快的执行。

  3. setImmediate会将现有的队列任务一次性读取到临时队列执行,所以在setImmediate中递归setImmediate不会阻塞JS执行。(见源码中QUEUE_MOVE方法)

    uv__run_check(loop);
    
    // 在每一轮循环中执行该函数,执行时机见uv_run
      void uv__run_##name(uv_loop_t* loop) {                                      \
        uv_##name##_t* h;                                                         \
        QUEUE queue;                                                              \
        QUEUE* q;                                                                 \
    
        // 把该类型对应的队列中所有节点摘下来挂载到queue变量
        QUEUE_MOVE(&loop->name##_handles, &queue);                                \
    
       // 遍历队列,执行每个节点里面的函数
        while (!QUEUE_EMPTY(&queue)) {                                            \
    
          // 取下当前待处理的节点
          q = QUEUE_HEAD(&queue);                                                 \
    
          // 取得该节点对应的整个结构体的基地址
          h = QUEUE_DATA(q, uv_##name##_t, queue);                                \
    
          // 把该节点移出当前队列
          QUEUE_REMOVE(q);                                                        \
    
         // 重新插入原来的队列
          QUEUE_INSERT_TAIL(&loop->name##_handles, q);                            \
    
         // 执行回调函数
          h->name##_cb(h);                                                        \
        }                                                                         \
      }
    
  4. 只要在check阶段之前加入队列的任务都会被执行。

# process.nextTick特性

  1. nextTick拥有自己的TickQueue队列,且优先级高于PromiseQueue
  2. process.nextTick会在当前宏任务执行完毕之后(调用栈清空,包括全局代码执行完毕)立即执行
  3. nextTick属于微任务,执行没有上限,所以如果递归调用容易造成JS阻塞。
  4. nextTick用于那些真正需要优先的操作,不能滥用。
上次更新: 2/13/2025, 3:29:47 AM