Skip to content

setTimeout/setInterval/setImmediate #108

Open
@yaofly2012

Description

@yaofly2012

一、setTimeout(function[, delay, arg1, arg2, ...]) / setInterval

setTimeout:告诉浏览器XXXms后向任务队列里插入个任务(callback);
setInterval:告诉浏览器每隔XXXms向任务队列里插入个任务(callback)。

1.1 语法

  1. 参数delay作为有符号的Int-32处理的
  • 对于非int32的实参会进行转int32
  • 对于非法的实参当做0处理
setTimeout(function(){
	console.log('hehe');
}, 3034101000)

会立马执行回调函数...
原因:上面的代码中指定的参数已经大于有符号的int-32最大值了,产生了溢出:

// 看看转成int-32的结果
3034101000>>0 // -1260866296,结果是个负数,setTimeout就当作0处理了

1.2 常见问题

1. 切到后天的tab,延时会进行节流处理(>1000ms)

浏览器的优化手段。

2. 回调函数执行时间大于指定的时间

  1. 浏览器本身带有4ms的节流处理
    保护电池,省电
  2. 本质上回调函数具体什么时候执行要看call stack运行情况。
    如果任务都比较耗时,很容造成任务队列的任务积压。
setTimeout(() => {
  console.log(`1, currentTime: ${Date.now()}`)
}, 100)

setTimeout(() => {
  console.log(`2, currentTime: ${Date.now()}`)
}, 200)

setTimeout(() => {
  console.log(`3, currentTime: ${Date.now()}`)
}, 300)

// 耗时操作,导致任务队列的任务积压
var last = Date.now();
while(Date.now() - last < 500) {}

3. 大于int-32的delay都会立马触发回调么?

浏览器端:

setTimeout(function(){
	console.log('hehe');
}, 30341010023)

30341010023也大于int-32最大值,但却没有立马执行。看来并不是大于int-32的delay都会立马触发回调。
setTimeout函数会先把delay参数先转成int-32。
30341010023转成int-32的值为:

console.log(30341010023>>>0); // 276238951 

Nodejs环境却是:
timers:

if (!(after >= 1 && after <= TIMEOUT_MAX)) {
    after = 1; // schedule on next tick, follows browser behaviour
  }

4. 超长时间延时

setTimeout/setInterval最大delay2^31 - 1(2,147,483,647 ms (大概24.8天))。如果超出该怎么办呢?
思路:切分为小段时间分片。
long-timeout

1.3 利用setTimeout(func, 0)或则setTimeout(func)创建异步任务

二、setImmediate

  1. 宏任务队列插入个任务。
  2. 代替setTimeout(callback, 0 [,param1, param2, ...]),因为setTimeout是依赖时间的,并且存在4ms的节流。但是在浏览器端兼容下比较差啊。

2.1 代替setTimeout(callback, 0 [,param1, param2, ...])

同样的效果,但执行过程不一样。使用setTimeout(func,0)的场景基本都是想着EventLoop里添加个异步任务,但setTimeout(func,0)的间接做到的:

  • 先告诉浏览器启个定时;
  • 当时间超过“0”ms(浏览器基本都是15ms)时,则把插入个异步任务。
  1. 更省电
  2. 总结下:

More efficient and consumes less power than the usual setTimeout(..., 0) pattern

2.2 polyfill

为啥宁愿优先采用window.postMessageMessageChannel,而最后才使用setTimeout(func, 0)?

                var now = performance.now();
                // setTimeout
                setTimeout(() => {
                    console.log(`setTimeout, span=${performance.now()- now}`)
                })

                // MessageChannel
                var messageChannel = new MessageChannel();
                messageChannel.port1.onmessage = () => {
                    console.log(`MessageChannel, span=${performance.now() - now}`)
                }
                messageChannel.port2.postMessage('hello');

                // postMessage
                window.onmessage = () => {
                    console.log(`PostMessage, span=${performance.now() - now}`)
                }
                window.postMessage('hello');

                // onReadyStateChange
                var html = document.documentElement;
                var script = document.createElement("script");
                script.onreadystatechange = function () {
                    console.log(`onerror, span=${performance.now() - now}`)
                    script.onreadystatechange = null;
                    html.removeChild(script);
                    script = null;
                }
                html.appendChild(script);
  1. 测试发现即使把setTimeout放最前面,执行的回调函数还是落后了:
    image

  2. Chrome下onReadyStateChange方案并不行(空script并没有触发readystatechange事件)。注释说是IE6-8,额,好吧,没有验证条件。

2.2 vs process.nextTick()

参考

  1. Why does setTimeout() “break” for large millisecond delay values?
  2. setTimeout fired immediately if delay is far future
  3. Scheduling: setTimeout and setInterval
  4. NodeJS - setTimeout(fn,0) vs setImmediate(fn)

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions