JavaScript setTimeout 如此不准确的原因是什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/21097421/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-27 20:02:44  来源:igfitidea点击:

What is the reason JavaScript setTimeout is so inaccurate?

javascriptsettimeout

提问by Tomas Prado

I got this code over here:

我在这里得到了这个代码:

var date = new Date();
setTimeout(function(e) {
    var currentDate = new Date();
    if(currentDate - date >= 1000) {
         console.log(currentDate, date);
         console.log(currentDate-date);
    }
    else {
       console.log("It was less than a second!");
       console.log(currentDate-date);
    }
}, 1000);

In my computer, it always executes correctly, with 1000 in the console output. Interestedly in other computer, the same code, the timeout callback starts in less than a second and the difference of currentDate - dateis between 980 and 998.

在我的计算机中,它始终正确执行,控制台输出为 1000。有趣的是,在其他计算机上,相同的代码,超时回调在不到一秒的时间内启动,差异currentDate - date在980和998之间。

I know the existence of libraries that solve this inaccuracy (for example, Tock).

我知道存在解决这种不准确性的库(例如,Tock)。

Basically, my question is: What are the reasons because setTimeoutdoes not fire in the given delay?Could it be the computer that is too slow and the browser automatically tries to adapt to the slowness and fires the event before?

基本上,我的问题是:在给定的延迟时间内没有触发的原因setTimeout什么会不会是电脑速度太慢,浏览器自动尝试适应速度慢,然后触发事件?

PS: Here is a screenshot of the code and the results executed in the Chrome JavaScript console:

PS:下面是在Chrome JavaScript控制台中执行的代码和结果截图:

Enter image description here

在此处输入图片说明

采纳答案by p.s.w.g

It's not supposed to be particularly accurate. There are a number of factors limiting how soon the browser can execute the code; quoting from MDN:

应该不是特别准确。有许多因素会限制浏览器执行代码的速度;引用自MDN

In addition to "clamping", the timeout can also fire later when the page (or the OS/browser itself) is busy with other tasks.

除了“钳位”之外,当页面(或操作系统/浏览器本身)忙于其他任务时,超时也可以稍后触发。

In other words, the way that setTimeoutis usually implemented, it is just meant to execute after a given delay, andonce the browser's thread is free to execute it.

换句话说,setTimeout通常的实现方式只是在给定的延迟后执行,并且一旦浏览器的线程可以自由执行它。

However, different browsers may implement it in different ways. Here are some tests I did:

但是,不同的浏览器可能会以不同的方式实现它。以下是我做过的一些测试:

var date = new Date();
setTimeout(function(e) {
    var currentDate = new Date();
    console.log(currentDate-date);
}, 1000);

// Browser Test1 Test2 Test3 Test4
// Chrome    998  1014   998   998
// Firefox  1000  1001  1047  1000
// IE 11    1006  1013  1007  1005

Perhaps the < 1000 times from Chrome could be attributed to inaccuracy in the Datetype, or perhaps it could be that Chrome uses a different strategy for deciding when to execute the code—maybe it's trying to fit it into the a nearest time slot, even if the timeout delay hasn't completed yet.

也许来自 Chrome 的 < 1000 次可能是由于Date类型不准确,或者也许是因为 Chrome 使用不同的策略来决定何时执行代码——也许它试图将它放入最近的时间段,即使超时延迟尚未完成。

In short, you shouldn't use setTimeoutif you expect reliable, consistent, millisecond-scale timing.

简而言之,setTimeout如果您期望可靠、一致的毫秒级计时,则不应使用。

回答by Niels Keurentjes

In general, computer programs are highly unreliable when trying to execute things with higher precision than 50 ms. The reason for this is that even on an octacore hyperthreaded processor the OS is usually juggling several hundreds of processes and threads, sometimes thousands or more. The OS makes all that multitasking work by scheduling all of them to get a slice of CPU time one after another, meaning they get 'a few milliseconds of time at most to do their thing'.

一般来说,当尝试以高于 50 毫秒的精度执行事物时,计算机程序是非常不可靠的。这样做的原因是,即使在八核超线程处理器上,操作系统通常也要处理数百个进程和线程,有时甚至数千个或更多。操作系统通过安排所有多任务处理来使所有多任务工作一个接一个地获得 CPU 时间,这意味着它们“最多只有几毫秒的时间来做他们的事情”。

Implicity this means that if you set a timeout for 1000 ms, chances are far from small that the current browser process won't even be running at that point in time, so it's perfectly normal for the browser not to notice until 1005, 1010 or even 1050 milliseconds that it should be executing the given callback.

这意味着,如果您将超时设置为 1000 毫秒,那么当前浏览器进程在该时间点甚至不会运行的可能性远非小,因此浏览器直到 1005、1010 或甚至 1050 毫秒,它应该执行给定的回调。

Usually this is not a problem, it happens, and it's rarely of utmost importance. If it is, all operating systems supply kernel level timers that are far moreprecise than 1 ms, and allow a developer to execute code at preciselythe correct point in time. JavaScript however, as a heavily sandboxed environment, doesn't have access to kernel objects like that, and browsers refrain from using them since it could theoretically allow someone to attack the OS stability from inside a web page, by carefully constructing code that starves other threads by swamping it with a lot of dangerous timers.

通常这不是问题,它会发生,而且很少是最重要的。如果是,所有的操作系统提供内核级的定时器,远远超过1毫秒精确,并允许开发人员在执行代码正是在正确的时间点。然而,JavaScript 作为一个高度沙盒的环境,无法访问这样的内核对象,浏览器也不会使用它们,因为它理论上可以允许某人通过仔细构建使其他人挨饿的代码从网页内部攻击操作系统的稳定性。通过用很多危险的计时器淹没它来消除线程。

As for why the test yields 980 I'm not sure - that would depend on exactly which browser you're using and which JavaScript engine. I can however fully understand if the browser just manually corrects a bit downwards for system load and/or speed, ensuring that "on average the delay is still about the correct time" - it would make a lot of sense from the sandboxing principle to just approximate the amount of time required without potentially burdening the rest of the system.

至于为什么测试产生 980 我不确定 - 这将取决于您使用的浏览器和 JavaScript 引擎。然而,我可以完全理解浏览器是否只是手动向下更正系统负载和/或速度,确保“平均延迟仍然是正确的时间” - 从沙盒原理到只是很有意义在不给系统其余部分带来潜在负担的情况下估算所需的时间量。

回答by Mathletics

Someone please correct me if I am misinterpreting this information:

如果我误解了这些信息,请有人纠正我:

According to a post from John Resigregarding the inaccuracy of performance tests across platforms (emphasis mine)

根据John Resig关于跨平台性能测试不准确的帖子(重点是我的)

With the system times constantly being rounded down to the last queried time (each about 15 ms apart) the quality of performance results is seriously compromised.

由于系统时间不断四舍五入到最后一次查询的时间(每间隔大约 15 毫秒),性能结果的质量受到严重影响。

So there is up toa 15 ms fudge on either end when comparing to the system time.

因此,与系统时间相比,两端最多有 15 毫秒的延迟。

回答by MikeTeeVee

I had a similar experience.
I was using something like this:

我有过类似的经历。
我正在使用这样的东西:

var iMillSecondsTillNextWholeSecond = (1000 - (new Date().getTime() % 1000));
setTimeout(function ()
{
    CountDownClock(ElementID, RelativeTime);
}, iMillSecondsTillNextWholeSecond);//Wait until the next whole second to start.

I noticed it would Skip a Second every couple Seconds, sometimes it would go for longer.
However, I'd still catch it Skipping after 10 or 20 Seconds and it just looked rickety.
I thought, "Maybe the Timeout is too slow or waiting for something else?".
Then I realized, "Maybe it's too fast, and the Timers the Browser is managing are off by a few Milliseconds?"

我注意到它会每隔几秒跳过一秒钟,有时会持续更长时间。
但是,我仍然会在 10 或 20 秒后发现它正在跳过,而且它看起来很摇摇晃晃。
我想,“也许超时太慢了还是在等待其他东西?”。
然后我意识到,“也许它太快了,浏览器管理的计时器关闭了几毫秒?

After adding +1 MilliSeconds to my Variable I only saw it skip once.
I ended up adding +50ms, just to be on the safe side.

在将 +1 MilliSeconds 添加到我的变量后,我只看到它跳过一次。
为了安全起见,我最终添加了 +50ms。

var iMillSecondsTillNextWholeSecond = (1000 - (new Date().getTime() % 1000) + 50);

I know, it's a bit hacky, but my Timer is running smooth now. :)

我知道,这有点笨拙,但我的计时器现在运行顺畅。:)