2014-10-21 7 views
5

Я столкнулся с неожиданным поведением JS setTimeout, когда модальные диалоговые окна, такие как alert, открыты, и я хотел бы знать причину этого.Почему модальные окна сообщений JS приостанавливают обратный отсчет на setTimeout()?

Я ожидал, что setTimeout (fn, 10000) означает «периодически проверять текущее время, а когда он больше, чем сейчас + 10000 мс, обработчик события, который будет вызывать переданную функцию« fn ». Это было бы логично, если бы мы теперь пропустили значение тайм-аута как «мс». Но, по-видимому, обратный отсчет на setTimeout является литеральным обратным отсчетом и будет приостановлен, пока модальное окно открыто.

setTimeout(function(){ 
    //alert A 
    alert("10 seconds have passed for the first setTimeout") 
}, 10000); 
setTimeout(function(){ 
    //alert B 
    alert("Wait for 15 seconds and press OK"); 
},1000); 

Я ожидал бы предупредить, чтобы сразу отобразить после закрытия предупреждения B (предполагая, что вы ждали в течение 15 сек., Чтобы сделать это), так как тревога тайм-аут был только на 10 секунд, и они уже прошли. Практика, однако, показывает, что обратный отсчет до предупреждения A просто приостанавливается, пока предупреждение B открыто, и оно будет отображаться только после ок. Еще 9 секунд прошло после того, как вы закрыли предупреждение B, независимо от того, как долго был открыт B.

Это не кажется логичным.

Обновление. Я определенно не единственная путаница здесь, потому что это поведение приостановки таймаута происходит в Chrome и Internet Explorer, но не в Firefox. Firefox выполняет поведение, которое я ожидал, - если вы ждете 15 секунд при оповещении B - предупреждение A всплывает мгновенно, когда вы его закрываете.

+0

Это одна из причин, почему 'alert's не хорошо, особенно с функциями синхронизации. – soktinpk

+0

@soktinpk Мы используем такие вещи, как Bootbox для оповещений в наших проектах, но бывают случаи, когда это неприменимо, например, когда вам нужно предупреждать пользователя о несохраненных данных после закрытия страницы. Я не могу представить, что люди часто сталкиваются с проблемами из-за этого поведения, но тем не менее интересна большая разница в поведении между тремя большими браузерами. –

+0

Предупреждение пользователя о несохраненных данных является вполне допустимым использованием для 'alert' и/или' confirm'. Однако почему вы хотите немного подождать до появления «предупреждения»? – soktinpk

ответ

5

Я сомневаюсь, что окончательный ответ на то, почему, как IE и Chrome поставить паузу в ожидании таймеры до alert были уволены, а Firefox - нет. Я считаю, что это только потому, что есть определенная свобода в интерпретации W3C's specs for alert:

настороже (сообщение) метод, при вызове, необходимо запустить следующие шаги:

  1. Если уровень вложенности завершения цикла событий в отличное от нуля, необязательно прервать эти шаги.

  2. Освободить мьютексы хранения.

  3. Показать данное сообщение пользователю.

  4. При необходимости приостанавливается при ожидании подтверждения пользователем сообщения .

Шаг 4 (пауза) далее объясняется here:

Некоторые из алгоритмов в данном описании, по историческим причинам, требуют агента пользователя, чтобы сделать паузу во время работы задача до тех пор, пока не будет достигнуто условие . Это означает, выполнив следующие действия:

  1. Если какие-либо асинхронно выполняющиеся алгоритмы ожидают стабильного состояния, затем запустить их синхронную секцию, а затем возобновит работу их асинхронный алгоритм. (См event loop обработки модели определение выше для деталей.)

  2. При необходимости обновлять интерфейс визуализации или пользователя любого документа или просмотра контекста, чтобы отражать текущее состояние.

  3. Ждать, пока не будет достигнута цель. Хотя пользовательский агент имеет приостановленную задачу , соответствующий цикл событий не должен запускать дополнительные задачи, и любой сценарий в текущей выполняемой задаче должен блокировать. Пользовательские агенты должны по-прежнему реагировать на ввод пользователя во время паузы, хотя и в уменьшенной емкости , поскольку цикл событий ничего не сделает.

Таким образом, цикл обработки событий получает приостановлено в любом случае. Обратный вызов более длительного таймаута не активируется, пока предупреждение все еще остается видимым и модальным. Если бы не так, всевозможные гадости могли бы стать возможными, например, несколько предупреждений друг на друга.

Теперь, можете ли вы рассказать по вышеприведенным спецификациям, следует ли приостановить отсчет таймера в течение всего срока действия предупреждения или скорее уволить, как только предупреждение исчезнет? Я не могу, и я даже не уверен, какое поведение будет более логичным.

Что я уверен в том, что вы не должны использовать предупреждения JavaScript для чего-либо еще, кроме отладки. Оповещения позволяют приостановить выполнение сценария (в то время как некоторые асинхронные операции, такие как XHR, происходят в фоновом режиме), но они довольно недружелюбны для пользователя. Правильный подход будет охватывать асинхронный код, используя обещания и, возможно, ES6 generators/yeild (если вы после стиля линейного кода).

Следующий вопрос является весьма связаны и некоторые альтернативы alert обсуждаются здесь:

-1

Предупреждение - это блокировка пользовательского интерфейса, и поскольку Javascript является одиночным, он блокирует что-либо от запуска до тех пор, пока диалог не будет отклонен.

+0

Вы хотели сказать, что «предупреждение синхронно». Я знаю это, но это не отвечает на вопрос. Мое первоначальное ожидание было для предупреждения A setTimeout, чтобы проверить текущее время сразу после того, как функция в предупреждении B возвращается, и поток JS разблокирован, и в этот момент он увидит, что прошло более 10000 мс и выполнит оповещение A. –

-1

Если вам действительно нужно, вы можете использовать свой собственный таймер.

var now = Date.now(); 
function alertA(){ 
    //alert A 
    alert("10 seconds have passed for the first setTimeout") 
} 
setTimeout(function(){ 
    //alert B 
    alert("Wait for 15 seconds and press OK"); 
    setTimeout(alertA, 10000 - (Date.now() - now)); // Subtract the time passed 
},1000); 

Вы можете обернуть это в методе полезности:

function alertDelay(messages, timePassed) { 
    if (typeof messages !== "object" || messages.length === 0) return; 
    if (timePassed == null) timePassed = 0; 
    var firstMessage = messages[0]; 
    var now = Date.now(); 
    setTimeout(function() { 
    alert(firstMessage.message); 
    alertDelay(messages.slice(1), Date.now() - now); 
    }, firstMessage.delay - timePassed); 
} 

Использование:

alertDelay([ 
    {message: 'Message 1', delay: 1000}, 
    {message: 'Message 2', delay: 5000}, 
    {message: 'Message 3', delay: 10000} 
]); 
+1

Я не ищу для обхода еще нет, мне просто интересно, почему. Этот код определенно не поможет, поскольку setTimeouts установлены в разных местах. Единственный работоспособный способ, который я могу придумать, это перегрузить window.setTimeout, чтобы использовать максимально возможный setInterval внутри, вести список обратных вызовов со сроками и проверять их против Date.now(). Должен работать, но я не думаю, что есть проект, который действительно нуждается в такой точности. В любом случае, как я уже сказал, больше интересуется «почему?». затем «как исправить?». –

+0

@IvanKoshelew Я думаю, что [ответ dnharjani] (http://stackoverflow.com/a/26487531/3371119) неплохо объясняет, почему: 'alert' может блокировать поток. Это действительно зависит от реализаций 'alert'. Вы не можете полагаться на него при использовании функций синхронизации. – soktinpk

+0

Я не думаю, что отвечает на исходный вопрос. Я знаю, почему он предотвращает обратный вызов setTimeout, пока он открыт, и не удивительно. Я не понимаю, почему он продолжает ждать дополнительных после того, как он закрыт. Почему только FireFox делает это так, как я ожидал? Должен ли я представить это как ошибку для других браузеров bugtrackers? Я имею в виду, так как они ведут себя по-разному с одним и тем же кодом - один из них должен быть неправильным. Вопрос в том, какой и почему? –