为什么 setTimeout 和 setiImidiate 仅在 IO 周期中是确定性的

why setTimeout and setiImidiate are deterministic only in IO cycle

在节点文档here中,据说同时调用setTimeout和setImmidiate的输出是不确定的。

我明白了,
但接下来是使用 IO 包装器回调的示例

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

这使得顺序始终为:

    sesetImmidiate 
    setTimeout

解释如下: 使用 setImmediate() 优于 setTimeout() 的主要优点是,如果在 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;
  }
  1. uv__loop_alive — Check whether there are any referenced handlers to be invoked, or any active operations pending
  2. uv__update_time — This will send a system call to get the current time and update the loop time (This is used to identify expired timers).
  3. uv__run_timers — Run all expired timers
  4. uv__run_pending — Run all completed/errored I/O callbacks
  5. uv__io_poll — Poll for I/O
  6. uv__run_check — Run all check handlers (setImmediate callbacks will run here)
  7. uv__run_closing_handles — Run all close handlers

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

If scripts have been scheduled by setImmediate(), "polling" phase will set a time-out which is zero.It means that after the queue has been exhausted, "polling" phase will not wait for callbacks to be added to the queue but continue to the check phase.

If scripts have been scheduled by setTimeout(), "polling" will set a time-out which is the result of the soonest threshold of timers minus current time.Then when time out, the loop continues and finally wraps back to the timers phase.