这个项目帮助解决了我一直在处理的应用程序中的一个主要问题,非常感谢您发布它。AdjustTickCount 实用程序是用于证明解决方案的出色且必不可少的工具,因此也对此表示赞赏。
该问题也影响 IE 7,它似乎也影响 IE 6,但后果更严重,因为浏览器停止响应,并且解决方案似乎也不适用于该版本。这些旧版本仍然有很多用户,特别是在商业/企业领域。
我没有发现鼠标左键是 Windows XP 中的一个因素,没有它就会出现问题。
如果超时延迟最多只有几秒钟,并且应用程序中设置的超时数量很少,那么前两个答案很好。如果需要更多和更长的超时,则需要做更多的工作来防止 Web 应用程序变得不可用。在使用诸如qooxdoo之类的框架的 Web 2.0 RIA 中,用户可能会使其运行数天,因此可能需要更多和更长的超时,而不是几个半秒的延迟来产生动画或其他短暂的效果。
第一个解决方案是一个好的开始,但是将下一个超时设置为target-now
将再次导致该函数被立即调用,因为这仍然会使正常运行时间+延迟超过 2^32 毫秒,因此 JS 代码将旋转直到正常运行时间回绕到 0 (或用户杀死浏览器)。
第二种解决方案是一种改进,因为过早超时只会以 1 秒的间隔发生,直到现在在正常运行时间环绕的 1 秒内,这允许其他代码查看但实验表明仍然足以使浏览器如果有足够的未决超时在播放,则无法使用。并且它仍然会持续到正常运行时间结束,所以如果请求的延迟足够长,用户仍然可能决定终止浏览器。
一个较少占用 CPU 的解决方案是将每个后续超时的延迟设置为前一个延迟持续时间的一半,直到该延迟小于 500 毫秒,此时我们知道正常运行时间的环绕点即将到来(< 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);
};
进一步细化:
我发现setTimeout()
即使系统正常运行时间不接近 2^32 毫秒,有时也可以比预期提前几毫秒调用回调。在这种情况下,上述函数中使用的下一个等待间隔可能大于到原始目标的剩余时间,这导致等待时间比最初预期的要长。
以下是解决此问题的另一个版本:
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);
};