我看到有很多关于此功能的文章,但没有一个能真正解决这个问题:我如何确保setTimeout在这些毫秒之后精确地精确运行?我正在编写一个对精度至关重要的应用程序,并且在很多地方阅读过,例如here,它考虑了我的同样问题,
setInterval()和setTimeout()的问题是没有 确保您的代码将在指定的时间运行。
那么,有什么办法可以迫使它运行到毫秒精度?我的应用程序实际上与我链接的主题的类型相同。
我已经在一个循环中运行了许多超时(我首先创建一个数组,一个数组一个接一个地运行超时),过一会儿系统不同步。
第二个问题,也许是:如果不是JavaScript,是否还有其他事情可能对此有所帮助?也许要使用WebGL?还是已经有一些js库正在考虑/解决这个问题?
编辑:我考虑过使用requestAnimationFrame,但是时间间隔可能在时间上有很大不同,并且我读到此函数是针对恒定步长进行的。
EDIT2:我在this页上找到了一个很好的算法,这是迄今为止我能找到的最好的算法。
答案 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运行时会在指定的时间间隔精确地调度计时器回调,因为它正在处理除该回调之外的许多其他任务。