当Windows系统接近49.7天的正常运行时间时,内部Windows毫秒计时器接近2 ^ 32。 在计算何时触发setInterval或setTimeout事件时,Internet Explorer 8中的错误似乎有算术溢出。例如,如果您在正常运行时间的第49天,并致电
setInterval(func, 86400000); // fire event in 24 hours
将立即调用func,而不是24小时。
如果将足够大的数字传递给setInterval或setTimeout,则可能会在25天正常运行时间(2 ^ 31毫秒)后的任何时间发生此错误。 (不过,我只在第49天检查过。)
您可以在命令行中输入“net statistics server”来检查正常运行时间。
有解决方法吗?
答案 0 :(得分:5)
您可以使用setTimeout
function setSafeTimeout(func, delay){
var target = +new Date + delay;
return setTimeout(function(){
var now = +new Date;
if(now < target) {
setSafeTimeout(func, target - now);
} else {
func();
}
}, delay);
}
这仍然会返回setTimeout
的值,因此如果未遇到错误,仍然可以使用clearTimeout
。如果clearTimeout
需要防弹,或者您需要setInterval
(并且可能是clearInterval
),则需要在问题上投入更多代码,但在执行{之前验证足够时间的原则已经过去了{ {1}}持有。
答案 1 :(得分:3)
这个项目帮助解决了我一直在努力的应用程序中的一个主要问题,所以发布它的重要谢谢。 AdjustTickCount实用程序是一个非常重要的工具,用于证明解决方案,所以也赞不绝口。
这个问题也会影响到IE 7,它似乎也会影响到IE 6,但由于浏览器停止响应并且解决方案似乎也无法在该版本上运行,因此会产生更糟糕的后果。这些旧版本的用户仍然很多,特别是在商业/企业领域。
我没有发现鼠标左键是Windows XP中的一个因素,没有它就会出现问题。
前两个答案很好,如果超时延迟最多只是几秒钟,并且应用程序中只设置了非常少量的超时。如果需要越来越长的超时,那么还有更多工作要做,以防止Web应用程序变得无法使用。在使用qooxdoo等框架的Web 2.0 RIA中,用户可能会使其运行数天,因此可能需要更长时间的超时,而不是产生动画或其他短暂效果的几秒半延迟。
第一个解决方案是一个好的开始,但是将下一个超时设置为target-now
将再次导致立即调用该函数,因为这仍将使正常运行时间+延迟超过2 ^ 32毫秒,因此JS代码将旋转直到正常运行时间变为0(或用户杀死浏览器)。
第二个解决方案是改进,因为过早超时只会以1秒的间隔发生,直到现在在正常运行时间的1秒内,允许其他代码进行查看但实验表明如果有足够的待处理超时时间仍然足以使浏览器无法使用。并且它仍然会持续到正常运行时间,所以如果请求的延迟足够长,用户可能仍然决定终止浏览器。
CPU消耗较少的解决方案是将每个后续超时的延迟设置为前一个延迟持续时间的一半,直到该延迟小于500毫秒,此时我们知道正常运行时间的环绕点即将来临(&lt; 1秒钟后我们可以将下一个超时设置为target-now
,以便仅在少量的进一步循环后停止过早超时检查。达到此目的所需的时间取决于原始延迟的持续时间以及调用setSafeTimeout
时正常运行时间的接近程度,但最终并且CPU负载最小时,应用程序将恢复正常行为而用户不会遇到任何问题长期放缓。
这样的事情:
function setSafeTimeout(func, delay) {
var target = +new Date + delay;
var newDelay = delay;
var helper = function()
{
var now = +new Date;
if (now < target)
{
newDelay /= 2; // halve the wait time and try again
if(newDelay < 500) // uptime wrap around is imminent
{
newDelay = target-now; // go back to using original target
}
var handle = setTimeout(helper, newDelay);
// if required record handle somewhere for clearTimeout
}
else
{
func();
}
};
return setTimeout(helper, delay);
};
进一步完善:
我发现即使系统正常运行时间不接近2 ^ 32ms,setTimeout()
有时也会比预期提前几毫秒调用回调。在这种情况下,上述函数中使用的下一个等待间隔可能大于原始目标的剩余时间,这导致比原始期望的更长的等待时间。
以下是解决此问题的另一个版本:
function setSafeTimeout(func, delay) {
var target = +new Date + delay;
var newDelay = delay;
var helper = function()
{
var now = +new Date;
if (now < target)
{
var timeToTarget = target-now;
newDelay /= 2; // halve the wait time and try again
if(newDelay < 500 || newDelay > timeToTarget) // uptime wrap around is imminent
{
newDelay = timeToTarget; // go back to using original target
}
var handle = setTimeout(helper, newDelay);
// if required record handle somewhere for clearTimeout
}
else
{
func();
}
};
return setTimeout(helper, delay);
};
答案 2 :(得分:1)
Cameron Jordan答案的一个变种:
function setSafeTimeout(func, delay) {
var target = +new Date + delay;
var helper = function() {
var now = +new Date;
if (now < target) {
setTimeout(arguments.callee, 1000);
} else {
func();
}
}
return setTimeout(helper, delay);
}
如果IE8处于错误状态,辅助函数的目的是每秒调用一次。
一个有用的测试工具是AdjustTickCount(仅限Windows XP)。例如,将新的刻度计数设置为0xffff0000将在刻度计数器翻转之前为您提供65秒的错误行为。任何设置为120秒的计时器都不会正常启动。
此外,在Windows XP中,错误的setTimeout行为似乎与单击鼠标左键相关,按照this post。