Perché setTimeout () "si interrompe" per valori di ritardo di millisecondi elevati?


104

Ho riscontrato un comportamento imprevisto durante il passaggio di un valore elevato in millisecondi a setTimeout(). Per esempio,

setTimeout(some_callback, Number.MAX_VALUE);

e

setTimeout(some_callback, Infinity);

entrambi fanno sì some_callbackche vengano eseguiti quasi immediatamente, come se avessi passato 0invece un gran numero come ritardo.

Perché succede questo?

Risposte:


143

Ciò è dovuto al fatto che setTimeout utilizza un int a 32 bit per memorizzare il ritardo, quindi il valore massimo consentito sarebbe

2147483647

se provi

2147483648

ottieni il tuo problema che si verifica.

Posso solo presumere che questo stia causando una qualche forma di eccezione interna nel motore JS e causando l'attivazione immediata della funzione piuttosto che per niente.


1
Ok, ha senso. Immagino che in realtà non sollevi un'eccezione interna. Invece, vedo che (1) causa un overflow intero o (2) forza internamente il ritardo a un valore int a 32 bit senza segno. Se (1) è il caso, allora sto davvero passando un valore negativo per il ritardo. Se è (2), delay >>> 0succede qualcosa di simile , quindi il ritardo passato è zero. In ogni caso, il fatto che il ritardo sia memorizzato come un int senza segno a 32 bit spiega questo comportamento. Grazie!
Matt Ball

Vecchio aggiornamento, ma ho appena scoperto che il limite massimo è 49999861776383( 49999861776384fa scattare la richiamata all'istante)
maxp

7
@maxp Questo perché49999861776383 % 2147483648 === 2147483647
David Da Silva Contín

@ DavidDaSilvaContín è davvero in ritardo, ma puoi spiegare ulteriormente? Non riesci a capire perché 2147483647 non è il limite?
Nick Coad

2
@NickCoad entrambi i numeri ritarderebbero lo stesso importo (ovvero 49999861776383 è uguale a 2147483647 da un punto di vista a 32 bit con segno). scriverli in binario e prendere gli ultimi 31 bit, saranno tutti 1s.
Mark Fisher

24

Puoi usare:

function runAtDate(date, func) {
    var now = (new Date()).getTime();
    var then = date.getTime();
    var diff = Math.max((then - now), 0);
    if (diff > 0x7FFFFFFF) //setTimeout limit is MAX_INT32=(2^31-1)
        setTimeout(function() {runAtDate(date, func);}, 0x7FFFFFFF);
    else
        setTimeout(func, diff);
}

2
questo è interessante, ma perdiamo la possibilità di usare ClearTimeout a causa della ricorsione.
Allan Nienhuis

2
Non si perde davvero la possibilità di annullarlo a condizione che si faccia la contabilità e si sostituisca il timeoutId si desidera annullare all'interno di questa funzione.
charlag

23

Alcune spiegazioni qui: http://closure-library.googlecode.com/svn/docs/closure_goog_timer_timer.js.source.html

Valori di timeout troppo grandi per essere contenuti in un numero intero a 32 bit con segno possono causare un overflow in FF, Safari e Chrome, con conseguente pianificazione immediata del timeout. Ha più senso semplicemente non pianificare questi timeout, poiché 24,8 giorni sono oltre una ragionevole aspettativa per il browser di rimanere aperto.


2
La risposta di warpech ha molto senso: un processo di lunga esecuzione come un server Node.JS potrebbe sembrare un'eccezione, ma ad essere onesti se hai qualcosa che vuoi assicurarti che avvenga esattamente in 24 e un po 'di giorni con precisione al millisecondo allora dovresti usare qualcosa di più robusto di fronte agli errori del server e della macchina rispetto a setTimeout ...
cfogelberg

@cfogelberg, non ho visto l'FF o qualsiasi altra implementazione del setTimeout(), ma spero che calcolino la data e l'ora in cui dovrebbe svegliarsi e non diminuiscano un contatore su un segno di spunta definito a caso ... (Si può sperare , almeno)
Alexis Wilke

2
Sto eseguendo Javascript in NodeJS su un server, 24,8 giorni è ancora buono, ma sto cercando un modo più logico per impostare una richiamata in modo che avvenga diciamo 1 mese (30 giorni). Quale sarebbe la strada da percorrere per questo?
Paul,

1
Di sicuro le finestre del browser sono state aperte per più di 24,8 giorni. È bizzarro per me che i browser non facciano internamente qualcosa di simile alla soluzione di Ronen, almeno fino a MAX_SAFE_INTEGER
acjay

1
Chi dice? Tengo il mio browser aperto per più di 24 giorni ...;)
Pete Alvin

2

Controlla il documento del nodo sui timer qui: https://nodejs.org/api/timers.html (assumendo lo stesso anche su js poiché è un termine così onnipresente ora basato sul ciclo di eventi

In breve:

Quando il ritardo è maggiore di 2147483647 o minore di 1, il ritardo verrà impostato su 1.

e il ritardo è:

Il numero di millisecondi da attendere prima di chiamare la richiamata.

Sembra che il tuo valore di timeout venga impostato per impostazione predefinita su un valore inaspettato secondo queste regole, forse?


1

Mi sono imbattuto in questo quando ho provato a disconnettere automaticamente un utente con una sessione scaduta. La mia soluzione era ripristinare il timeout dopo un giorno e mantenere la funzionalità per utilizzare clearTimeout.

Ecco un piccolo esempio di prototipo:

Timer = function(execTime, callback) {
    if(!(execTime instanceof Date)) {
        execTime = new Date(execTime);
    }

    this.execTime = execTime;
    this.callback = callback;

    this.init();
};

Timer.prototype = {

    callback: null,
    execTime: null,

    _timeout : null,

    /**
     * Initialize and start timer
     */
    init : function() {
        this.checkTimer();
    },

    /**
     * Get the time of the callback execution should happen
     */
    getExecTime : function() {
        return this.execTime;
    },

    /**
     * Checks the current time with the execute time and executes callback accordingly
     */
    checkTimer : function() {
        clearTimeout(this._timeout);

        var now = new Date();
        var ms = this.getExecTime().getTime() - now.getTime();

        /**
         * Check if timer has expired
         */
        if(ms <= 0) {
            this.callback(this);

            return false;
        }

        /**
         * Check if ms is more than one day, then revered to one day
         */
        var max = (86400 * 1000);
        if(ms > max) {
            ms = max;
        }

        /**
         * Otherwise set timeout
         */
        this._timeout = setTimeout(function(self) {
            self.checkTimer();
        }, ms, this);
    },

    /**
     * Stops the timeout
     */
    stopTimer : function() {
        clearTimeout(this._timeout);
    }
};

Uso:

var timer = new Timer('2018-08-17 14:05:00', function() {
    document.location.reload();
});

E puoi cancellarlo con il stopTimermetodo:

timer.stopTimer();

0

Non posso commentare ma rispondere a tutte le persone. Prende un valore senza segno (ovviamente non puoi aspettare millisecondi negativi) Quindi poiché il valore massimo è "2147483647" quando inserisci un valore più alto, inizia da 0.

Fondamentalmente ritardo = {VALUE}% 2147483647.

Quindi l'utilizzo del ritardo di 2147483648 renderebbe 1 millisecondo, quindi, proc istantaneo.


-2
Number.MAX_VALUE

in realtà non è un numero intero. Il valore massimo consentito per setTimeout è probabilmente 2 ^ 31 o 2 ^ 32. Provare

parseInt(Number.MAX_VALUE) 

e ne ottieni 1 invece di 1.7976931348623157e + 308.


13
Questo non è corretto: Number.MAX_VALUEè un numero intero. È il numero intero 17976931348623157 con 292 zeri dopo. Il motivo per cui parseIntritorna 1è perché prima converte il suo argomento in una stringa e poi cerca la stringa da sinistra a destra. Non appena trova il .(che non è un numero), si ferma.
Pauan

1
A proposito, se vuoi verificare se qualcosa è un numero intero, usa la funzione ES6 Number.isInteger(foo). Ma poiché non è ancora supportato, puoi usare Math.round(foo) === fooinvece.
Pauan

2
@ Pauan, per quanto riguarda l'implementazione, Number.MAX_VALUEnon è un numero intero ma un file double. Quindi c'è che ... Un double può rappresentare un numero intero, poiché viene utilizzato per salvare interi di 32 bit in JavaScript.
Alexis Wilke

1
@AlexisWilke Sì, ovviamente JavaScript implementa tutti i numeri come virgola mobile a 64 bit. Se per "intero" intendi "binario a 32 bit", Number.MAX_VALUEnon è un numero intero. Ma se per "intero" intendi il concetto mentale di "un intero", allora è un numero intero. In JavaScript, poiché tutti i numeri sono in virgola mobile a 64 bit, è comune utilizzare la definizione del concetto mentale di "intero".
Pauan

C'è anche Number.MAX_SAFE_INTEGERma non è nemmeno il numero che stiamo cercando qui.
tremby
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.