2014-10-21 34 views
5

Ho riscontrato un comportamento imprevisto di JS setTimeout quando finestre di dialogo modali come alert sono aperte e vorrei sapere il motivo.Perché le finestre dei messaggi modali JS sospendono il conto alla rovescia su setTimeout()?

Mi aspettavo che setTimeout (fn, 10000) significhi "controllare l'ora corrente periodicamente e quando è maggiore di Ora + 10000 ms attiva il gestore di eventi che invocherà la funzione 'fn' passata". Sarebbe logico vedere come passiamo la misura del timeout come "ms da ora". Ma, a quanto pare, il conto alla rovescia su setTimeout è un conto alla rovescia letterale e verrà messo in pausa mentre una finestra modale è aperta.

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); 

mi aspetterei avviso A per visualizzare subito dopo la chiusura avviso B (presumendo che si aspettato per 15 sec. A farlo), dal momento che avvisare un timeout era solo per 10 secondi e sono già passato. La pratica, tuttavia, mostra che il conto alla rovescia per avvisare A viene semplicemente messo in pausa mentre l'avviso B è aperto e verrà mostrato solo dopo ca. Sono passati 9 secondi in più dopo che hai chiuso l'avviso B, indipendentemente dal tempo in cui B era aperto.

Questo non sembra logico.

Aggiornamento . Non sono sicuramente l'unico confuso qui, perché questo comportamento di sospendere il timeout si verifica in Chrome e Internet Explorer, ma non in Firefox. Firefox esegue il comportamento che mi aspettavo - se si attende per 15 secondi in caso di avviso B - l'avviso A si apre istantaneamente quando lo si chiude.

+0

Questo è uno dei motivi per cui 'alert's non sono buone soprattutto con funzioni di temporizzazione. – soktinpk

+0

@soktinpk Usiamo cose come Bootbox per avvisi nei nostri progetti, ma ci sono momenti in cui non è applicabile, come quando è necessario avvisare l'utente dei dati non salvati alla chiusura della pagina. Non riesco a immaginare persone che si trovano nei guai a causa di questo comportamento troppo spesso, eppure, una grande differenza di comportamento tra i tre grandi browser è interessante. –

+0

Avviso un utente sui dati non salvati è un uso perfettamente valido per 'alert' e/o' confirm'. Tuttavia, perché dovresti aspettare un po 'prima che venga visualizzato l'avviso? – soktinpk

risposta

5

Dubito che ci sia una risposta definitiva sul perché sia ​​IE e Chrome messo una pausa in attesa i timer fino al alert sono stati ignorati e Firefox no. Credo che sia solo perché non v'è certa libertà nell'interpretazione del W3C's specs for alert:

L'avviso (messaggio) metodo, quando invocata, devono essere eseguiti i seguenti passaggi:

  1. Se livello di annidamento cessazione del ciclo degli eventi è diverso da zero, facoltativamente annulla questi passaggi.

  2. Rilasciare il mutex di archiviazione.

  3. Mostra il messaggio indicato all'utente.

  4. Opzionalmente, mettere in pausa in attesa che l'utente riconosca il messaggio .

Fase 4 (la pausa) è ulteriormente spiegato here:

Alcuni degli algoritmi in questa specifica, per ragioni storiche, richiedono l'agente utente per mettere in pausa durante l'esecuzione di un compito fino a quando viene raggiunto l'obiettivo . Ciò significa eseguendo i seguenti passaggi:

  1. Se alcuni algoritmi asincrono-esecuzione attendono uno stato stabile, quindi eseguire la loro sezione sincrono e poi riprendere l'esecuzione loro algoritmo asincrono. (Vedere la event loop elaborazione modello di definizione di cui sopra per i dettagli.)

  2. Se necessario, aggiornare l'interfaccia utente resa o di qualsiasi contesto Documento o la navigazione in modo da riflettere lo stato attuale.

  3. Attendere fino al raggiungimento dell'obiettivo di condizione. Mentre un agente utente ha un'attività in pausa , il ciclo di eventi corrispondente non deve eseguire ulteriori attività e qualsiasi script nell'attività in esecuzione deve essere bloccato. I programmi utente dovrebbero rimanere reattivi all'input dell'utente mentre sono in pausa, tuttavia, anche se in una capacità ridotta di poiché il ciclo degli eventi non farà nulla.

Così, il ciclo degli eventi viene sospeso in ogni caso. Il callback del timeout più lungo non viene richiamato mentre l'avviso è ancora visibile e modale. Se non fosse stato così, tutti i tipi di fastidi potrebbero essere resi possibili, come più avvisi uno sopra l'altro.

Ora, si può dire dalle specifiche di cui sopra se il conto alla rovescia del timer deve essere sospeso per tutta la durata dell'avviso, o piuttosto dovrebbe essere sparato non appena l'avviso è andato? Non posso, e non sono nemmeno sicuro di quale sarebbe il comportamento più logico.

Ciò di cui sono sicuro è che non si dovrebbero usare avvisi JavaScript per scopi diversi dal debug. Gli avvisi consentono di sospendere l'esecuzione di script (mentre alcune operazioni asincrone come XHR si svolgono in background), ma sono piuttosto ostili all'utente. L'approccio corretto sarebbe quello di adottare un codice asincrono, usando promesse e, possibilmente, ES6 generators/yeild (se si segue lo stile del codice lineare).

La domanda è altamente correlato e alcune alternative alla alert vi sono discussi:

-1

l'avviso è il blocco dell'interfaccia utente e poiché Javascript è a thread singolo bloccherà qualsiasi cosa dall'esecuzione finché la finestra di dialogo non viene chiusa.

+0

Intendevi dire che "l'avviso è sincrono". Lo so, ma questo non risponde alla domanda. La mia aspettativa originaria era un avviso Un setTimeout per controllare l'ora corrente subito dopo che la funzione nell'avviso B ritorna e il thread JS è sbloccato, a quel punto si vedrebbe che oltre 10000 ms sono passati ed eseguito l'avviso A. –

-1

Se è davvero necessario, è possibile utilizzare il proprio timer.

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); 

Puoi avvolgere questo in un metodo di utilità:

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); 
} 

Usage:

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

Non sto guardando per una soluzione ancora, sono solo curioso di sapere perché. Questo codice sicuramente non sarebbe di grande aiuto, dal momento che setTimeouts è impostato in luoghi diversi. L'unico modo praticabile che mi viene in mente sarebbe quello di sovraccaricare window.setTimeout per utilizzare setInterval più veloce possibile, mantenere un elenco di callback con date di scadenza e controllarli rispetto a Date.now(). Dovrebbe funzionare, ma non penso che ci sia un progetto che avrebbe davvero bisogno di una tale precisione. Ad ogni modo, come ho detto, più interessato a "perché?" allora 'come risolvere?'. –

+0

@IvanKoshelew Penso che [la risposta di dnharjani] (http://stackoverflow.com/a/26487531/3371119) faccia un buon lavoro di spiegazione del perché: 'alert' può bloccare il thread. Dipende davvero dalle implementazioni di 'alert'. Non puoi fare affidamento su di esso quando usi le funzioni di cronometraggio. – soktinpk

+0

Non penso che risponda alla domanda originale. So perché impedisce il callback setTimeout mentre è aperto - non c'è da stupirsi. Quello che non capisco è perché continua ad aspettare in più dopo che è stato chiuso. Perché solo FireFox fa ciò che mi aspettavo? Devo inviare questo come bug ad altri bugtracker del browser? Voglio dire, dal momento che si comportano in modo diverso con lo stesso codice - uno di loro deve essere sbagliato. La domanda è: quale e perché? –