nodejs:在setTimeout(fn,0)与nodejs doc描述不一致后执行的setImmediate回调

时间:2015-01-29 08:18:03

标签: javascript node.js settimeout event-loop

这就是Node document said

  

setImmediate(callback,[arg],[...])

     

安排I / O事件回调后“立即”执行回调,并在setTimeout和setInterval 之前安排。返回一个immediateObject,可以与clearImmediate()一起使用。您也可以选择将参数传递给回调。

但是,如果您运行以下代码,您将发现结果不符合预期。

var fs = require('fs');
console.time('start');
setImmediate(function () {
  console.log('immediate');
  console.timeEnd('start');
});

setTimeout(function() {
  console.log('timeout');
  console.timeEnd('start');
}, 0);

process.nextTick(function () {
  console.log('nextTick');
  console.timeEnd('start');
});

输出如下,setImmediate回调刚刚在 setTimeout回调后执行。那么我是否误解了Node文档中的内容?

  

nextTick
  开始:2ms
  超时
  开始:2ms
  立即
  开始:2ms

1 个答案:

答案 0 :(得分:2)

正如Node.js doc所说:

  

setimmediate-vs-settimeout   执行计时器的顺序将根据调用它们的上下文而有所不同。如果从主模块中调用两者,那么时间将受到进程性能的限制(可能受到计算机上运行的其他应用程序的影响)。   例如,如果我们运行不在I / O周期内的以下脚本(即主模块),则执行两个定时器的顺序是非确定性的,因为它受进程性能的约束:

为什么?。

node.js中的每个事件都由libuv的uv_run()函数驱动。部分代码

int uv_run(uv_loop_t* loop, uv_run_mode mode) {
  int timeout;
  int r;
  int ran_pending;

  r = uv__loop_alive(loop);
  if (!r)
    uv__update_time(loop);

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

    ......

    uv__io_poll(loop, timeout);
    uv__run_check(loop);
    uv__run_closing_handles(loop);
    ............

所以正如node.js Doc所解释的那样,我们可以匹配代码中事件循环的每个阶段。

计时器阶段 uv__run_timers(loop);

I / O回调 ran_pending = uv__run_pending(loop);

空闲/准备 uv__run_idle(loop); uv__run_prepare(loop);

民意调查 uv__io_poll(loop, timeout);

检查 uv__run_check(loop);

关闭回调 uv__run_closing_handles(loop);

超过阶段

但是如果我们在循环进入定时器阶段之前看到代码,它会调用 uv__update_time(loop);初始化循环时间。

void uv_update_time(uv_loop_t* loop) {
   uv__update_time(loop);
}

uv__update_time(loop)调用函数uv__hrtime

会发生什么
UV_UNUSED(static void uv__update_time(uv_loop_t* loop)) {
  /* Use a fast time source if available.  We only need millisecond precision.
   */
  loop->time = uv__hrtime(UV_CLOCK_FAST) / 1000000;
}

uv__hrtime的此调用取决于平台,并且在制作系统时耗费大量时间 致电clock_gettime。它受到机器上运行的其他应用程序的影响。

#define NANOSEC ((uint64_t) 1e9)
uint64_t uv__hrtime(uv_clocktype_t type) {
  struct timespec ts;
  clock_gettime(CLOCK_MONOTONIC, &ts);
  return (((uint64_t) ts.tv_sec) * NANOSEC + ts.tv_nsec);
}

返回此参数后,将在事件循环中调用计时器阶段。

void uv__run_timers(uv_loop_t* loop) {
  ...
  for (;;) {
    ....
    handle = container_of(heap_node, uv_timer_t, heap_node);
    if (handle->timeout > loop->time)
      break;
      ....
    uv_timer_again(handle);
    handle->timer_cb(handle);
  }
}

如果循环的当前时间大于超时,则运行定时器阶段中的回调。 需要注意的另一个重要事项是setTimeout设置为0时内部转换为1。 同样作为hr_time返回时间(以纳秒为单位),timeout_vs_immediate.js所示的此行为现在变得更加明确。

如果第一个循环之前的准备时间超过1ms,那么Timer阶段将调用与之关联的回调。如果它小于1ms事件循环继续到下一阶段,并在循环的检查阶段运行setImmediate回调,并在setTimeout中运行 循环的下一个滴答。