Этот пункт помог решить большую головную боль в приложении, над которым я работал, поэтому большое спасибо за публикацию. Утилита AdjustTickCount - это фантастический и необходимый инструмент для проверки решений, так что не забывайте и об этом.
Эта проблема также затрагивает IE 7 и, похоже, также влияет на IE 6, но с худшими последствиями, поскольку браузер перестает отвечать всем вместе, и решения, похоже, не работают и в этой версии. Все еще есть много пользователей этих старых версий, особенно в мире бизнеса / предпринимательства.
Я не обнаружил, что левая кнопка мыши была фактором в Windows XP, проблема возникает без него.
Первые два ответа хороши, если задержка тайм-аута составляет самое большее несколько секунд и в приложении установлено очень небольшое количество тайм-аутов. Если требуется все больше и больше тайм-аутов, нужно сделать еще больше, чтобы веб-приложение перестало работать. В RIA Web 2.0 с использованием инфраструктуры, такой как qooxdoo , пользователи могут оставлять ее включенной на несколько дней, поэтому может возникнуть большая потребность в большем и большем времени ожидания, чем несколько полсекундных задержек для создания анимации или другого краткого эффекта.
Первое решение - хорошее начало, но установка следующего тайм-аута на target-now
снова вызовет немедленный вызов функции, потому что это все равно сделает время безотказной работы + задержку более 2 ^ 32 миллисекунд, и, следовательно, код JS будет вращаться до тех пор, пока время безотказной работы приближается к 0 (или пользователь убивает браузер).
Второе решение является улучшением, потому что преждевременные тайм-ауты будут происходить только с интервалами в 1 секунду до тех пор, пока сейчас не будет в течение 1 секунды после циклического изменения времени работы, что позволяет другому коду заглядывать, но эксперименты показали этого может быть достаточно, чтобы сделать браузер непригодным для использования, если в игре достаточно ожидающих таймаутов. И это будет продолжаться до тех пор, пока время работы не будет сокращено, поэтому, если запрошенная задержка достаточно велика, пользователь все равно может решить закрыть браузер.
Менее ресурсоемким решением является установка задержки для каждого последующего тайм-аута равной половине продолжительности предыдущей задержки до тех пор, пока эта задержка не станет меньше 500 мс, и тогда мы узнаем, что точка перехода в рабочее состояние неизбежна (<1 второй раз), и мы можем установить следующий тайм-аут на <code>target-now, чтобы преждевременная проверка тайм-аута прекращалась только после небольшого числа последующих циклов. Сколько времени потребуется, чтобы достичь этого, будет зависеть от того, как долго была первоначальная задержка, и насколько близко было время перехода в рабочее время при вызове setSafeTimeout
, но в конечном итоге и при минимальной загрузке ЦП приложение возвращается к нормальному поведению без какого-либо длительного замедления пользователя .
Примерно так:
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);
};