在节点文档here中,可以说一起调用setTimeout和setImmidiate的输出是不确定的。

我明白那个,
 但是下面是使用IO包装器回调的示例

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


这使得订单始终为:

    sesetImmidiate
    setTimeout


有以下说明:
与setTimeout()相比,使用setImmediate()的主要优点是,如果在I / O周期内安排了任何计时器,则setImmediate()将始终在任何计时器之前执行,而与存在多少计时器无关。

为什么恰好“如果在I / O周期内安排了setImmediate(),它将始终在任何计时器之前执行”?

最佳答案

这是由于libuv设计的原因,在this文章中,您可以找到有关其工作原理的完整说明,以下是摘要:

Libuv订单执行:

while (r != 0 && loop->stop_flag == 0) {
     // first timers
    uv__update_time(loop);
    uv__run_timers(loop);
    ran_pending = uv__run_pending(loop);
    uv__run_idle(loop);
    uv__run_prepare(loop);

    timeout = 0;
    if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT) {
      timeout = uv_backend_timeout(loop);
    }

    uv__io_poll(loop, timeout);
    uv__run_check(loop);           // check handlers - "setImmediate"
    uv__run_closing_handles(loop);

    if (mode == UV_RUN_ONCE) {
     // second timers
      uv__update_time(loop);
      uv__run_timers(loop);
    }

    r = uv__loop_alive(loop);
    if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)
      break;
  }



  
  uv__loop_alive-检查是否有任何要引用的处理程序
  调用,或任何活动操作挂起
  uv__update_time —此
  将发送系统调用以获取当前时间并更新循环
  时间(用于标识过期的计时器)。
  uv__run_timers —运行
  所有过期的计时器
  uv__run_pending-运行所有已完成/错误的I / O
  回调
  uv__io_poll — I / O的轮询
  uv__run_check —运行所有检查
  处理程序(setImmediate回调将在此处运行)
  uv__run_closing_handles —运行所有关闭处理程序
  


setTimeoutsetImmediate都是宏任务,这就是为什么要按此顺序执行的原因,有关此here的讨论很好:


  如果脚本已由setImmediate()安排,则“轮询”阶段将
  将超时设置为零,这意味着在队列已
  筋疲力尽,“轮询”阶段将不会等待将回调添加到
  排队,但继续进行检查阶段。
  
  如果脚本已经
  由setTimeout()安排,“轮询”将设置一个超时,即
  计时器最快阈值减去当前时间的结果。
  超时,循环继续进行,最后回绕到计时器
  相。

关于javascript - 为什么setTimeout和setiImidiate仅在IO周期中是确定性的,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/56153710/

10-09 13:05