定时器在浏览器中和nodejs中的差异??
发布于 8 年前 作者 FantasyGao 2773 次浏览 来自 问答
如下一段代码分别在 chrome控制台中调试和nodejs环境下调试
(function testSetInterval() {
    let i = 0;
    const start = Date.now();
    const timer = setInterval(() => {
        i += 1;
        i === 5 && clearInterval(timer);
        console.log(`第${i}次开始`, Date.now() - start);
        for(let i = 0; i < 100000000; i++) {}
        console.log(`第${i}次结束`, Date.now() - start);
    }, 100);
})();
在浏览器下的结果:每次结束便立即开始执行,基本没有消耗时间。
VM117:7 第1次开始 111
VM117:9 第1次结束 1095
VM117:7 第2次开始 1096
VM117:9 第2次结束 2043
VM117:7 第3次开始 2043
VM117:9 第3次结束 3010
VM117:7 第4次开始 3010
VM117:9 第4次结束 3923
VM117:7 第5次开始 3939
VM117:9 第5次结束 4911
在nodejs下的结果 :每次结束到下次开始都要等100ms左右
第1次开始 140
第1次结束 1281
第2次开始 1390
第2次结束 2545
第3次开始 2654
第3次结束 3777
第4次开始 3902
第4次结束 5010
第5次开始 5119
第5次结束 6274
经过我查资料和思考认为是在浏览器内,定时器在每过100ms就要执行了,不会管前面任务的占用的时间,但是由于被前一个任务占着主线程,所以出现第一个结束和第二个开始几乎同时执行,而nodejs要等待第一个结束完之后再开始进入进入第二个,不知道理解的对不对。求各位前辈不吝赐教,讲一下这个是为什么,多谢了。
5 回复

求大神解答下,给点思路。

你的理解大致是对的,在 node 中负责 timer 计算的底层模块是 libuv 中的 uv_run_timers,精简后代码如下:

void uv__run_timers(uv_loop_t* loop) {
  //省略...
  for (;;) {
    //省略...
    if (handle->timeout > loop->time)
      break;
    uv_timer_stop(handle);
    uv_timer_again(handle);
	//执行 interval 中注册的 js 回调的地方
    handle->timer_cb(handle);
  }
}

现象产生的原因也比较明显了:

  • 每次计算 event_loop 打的 hrtime 时间戳小于超时的 handle->timeout 后,认为定时器到时开始执行
  • handle->timer_cb(handle) timer 中的回调执行完毕后,重新给 event_loop 打上 hrtime 时间戳,并且计算下一个到时的定时器时间

因此每次当你的 interval 中注册的回调结束完后,其实才会设置下一个定时器,因为你会看到执行完后继续等待 100ms 左右才会执行下一次

@hyj1991 多谢了,很清晰。还有想问一下,发生这个差异的原因是浏览器是新开了其他线程处理定时器,而nodejs里面的是底层的libuv处理的定时器,是这样吗?

@FantasyGao 浏览器我不清楚,node的定时器分为两部分,一部分是js层构造了链表,但是真正驱动是在底层的libuv中驱动的,关于js层构造的链表,可以看下我之前的文章:

浅探Node中定时器模块构造的js链表

@hyj1991 好的 多谢多谢

回到顶部