setTimeout计时精度

时间:2018-08-19 10:15:14

标签: javascript

我看到有很多关于此功能的文章,但没有一个能真正解决这个问题:我如何确保setTimeout在这些毫秒之后精确地精确运行?我正在编写一个对精度至关重要的应用程序,并且在很多地方阅读过,例如here,它考虑了我的同样问题,

  

setInterval()和setTimeout()的问题是没有   确保您的代码将在指定的时间运行。

那么,有什么办法可以迫使它运行到毫秒精度?我的应用程序实际上与我链接的主题的类型相同。

我已经在一个循环中运行了许多超时(我首先创建一个数组,一个数组一个接一个地运行超时),过一会儿系统不同步。

第二个问题,也许是:如果不是JavaScript,是否还有其他事情可能对此有所帮助?也许要使用WebGL?还是已经有一些js库正在考虑/解决这个问题?

编辑:我考虑过使用requestAnimationFrame,但是时间间隔可能在时间上有很大不同,并且我读到此函数是针对恒定步长进行的。

EDIT2:我在this页上找到了一个很好的算法,这是迄今为止我能找到的最好的算法。

3 个答案:

答案 0 :(得分:1)

这是一个很好地说明它的线程-通常与Javascript和浏览器有关:What is the reason JavaScript setTimeout is so inaccurate?

通过创建一个依赖于系统时间的自我调整计时器,您可能会获得更好的结果,但是即使那样,延迟可能仍然很小(自我调整会再次发生)

function doTimer(length, resolution, oninstance, oncomplete)
{
var steps = (length / 100) * (resolution / 10),
    speed = length / steps,
    count = 0,
    start = new Date().getTime();

function instance()
{
    if(count++ == steps)
    {
        oncomplete(steps, count);
    }
    else
    {
        oninstance(steps, count);

        var diff = (new Date().getTime() - start) - (count * speed);
        window.setTimeout(instance, (speed - diff));
    }
}

window.setTimeout(instance, speed);
}

来源:https://www.sitepoint.com/creating-accurate-timers-in-javascript/

答案 1 :(得分:1)

Js有3个微任务队列,分别是setTimeout/Interval/Immediate(有人称这些宏任务,等等),requestAnimationFrame rAF )和新孩子{ {1}}。承诺尽快解决,如果setTimeouts是嵌套的(且深度超过5层),则连续调用之间的最小间隔为4ms,rAF每秒将执行约60帧。

在这些rAF中,它们知道document.hidden状态,并且大约每17ms(理论上为16.67)执行一次。如果您希望的间隔大于此值,请使用rAF解决。

rAF的问题在于,因为它每隔〜17ms执行一次,所以如果我想以100 ms的间隔执行某项操作,那么在5个滴答声之后,我将处于〜85ms,在第六个滴答声时,我将处于102ms。我可以在102毫秒处执行,但是我需要从下一个调用时间开始降低2毫秒。这将防止相对于您指定的帧意外地“退出”回调。您可以大致设计一个接受选项对象的函数:

Promises

该对象在每次调用时都会回收并更新。将粘贴内容复制到您的控制台上,然后尝试:

function wait(t,options){
    if(!options){
        options = t;
        window.requestAnimationFrame(function(t){
            wait(t,options);
        });
        return options;
    }
    if(!options._set) {
        options.startTime = options.startTime || t;
        options.relativeStartTime = options.startTime;
        options.interval = options.interval || 50;
        options.frame = options.frame || 0;
        options.callback = options.callback || function(){};
        options.surplus = options.surplus || 0;
        options._set = true;
    }
    options.cancelFrame = window.requestAnimationFrame(function(t){
        wait(t,options);
    });
    options.elapsed = t - options.relativeStartTime + options.surplus;
    if (options.elapsed >= options.interval) {
        options.surplus = options.elapsed % options.interval;
        options.lastInvoked = t;
        options.callback.call(options);
        options.frame++;
        options.relativeStartTime = t;
    }
    return options;
}

使用var x = wait({interval:190,callback:function(){console.log(this.lastInvoked - this.relativeStartTime)}}) 指向选项对象执行回调。返回的x是options对象本身。要取消运行,请执行以下操作:

this

这不一定总是像间隔一样,您也可以像setTimeout一样使用它。假设您的变量框架是您所说的32的倍数,在这种情况下扩展options对象:

window.cancelAnimationFrame(x.cancelFrame);

我在选项对象中添加了frameList键。这是我们要执行回调的一些时间值。我们从96开始,然后进入frameList数组,32、32、64等。如果运行上面的代码,则会得到:

var x = wait({frameList:[32,32,64,128,256,512,1024,2048,32,128],interval:96,callback:function(){
    console.log(this.lastInvoked - this.relativeStartTime);
    window.cancelAnimationFrame(this.cancelFrame);
    this.interval = this.frameList[this.frame];
    if(this.interval){
        wait(this);
    }
}})

这些就是我对您遇到的情况的看法。

它尽可能接近指定的间隔运行。如果您设置非常接近的间隔(例如28、30、32),则将无法通过肉眼检查差异。也许尝试通过控制台记录“盈余”值,如下所示:

99.9660000000149
33.32199999992736
33.32199999992736
66.64400000008754
133.28799999994226
249.91499999980442
517.7960000000894
1016.5649999999441
2049.7950000001583
33.330000000074506
133.31999999983236

在不同的时间间隔内,您会看到略有不同的数字,并且这些数字会随着时间推移而变化,因为我们正在防止“逐步淘汰”。最终测试将是查看一定数量的“帧”所花费的平均时间:

var x = wait({interval:28,callback:function(){
    console.log(this.surplus);
}})

例如,如果将间隔更改为32,它将记录大约3200ms等。总而言之,我们设计的功能不应该取决于实时时间,它应该从js引擎获取当前当前的帧并应以此为基础。

答案 2 :(得分:0)

不,实际上是不可能的。 Javascript运行时环境(例如v8)没有实时保证,也不能在实时操作系统上运行它们。

基本上,不能保证由宿主操作系统的调度程序按时调度javascript运行时,也不能保证Javascript运行时会在指定的时间间隔精确地调度计时器回调,因为它正在处理除该回调之外的许多其他任务。