141 votes

Pourquoi setTimeout() "s'arrête" pour les grandes valeurs de délai en millisecondes ?

Je suis tombé sur un comportement inattendu en passant une grande valeur de milliseconde à setTimeout() . Par exemple,

setTimeout(some_callback, Number.MAX_VALUE);

y

setTimeout(some_callback, Infinity);

les deux causent some_callback pour être exécuté presque immédiatement, comme si j'avais passé 0 au lieu d'un grand nombre comme le délai.

Pourquoi cela se produit-il ?

1 votes

Parce que c'est limité à 32 bits, ce qui est 2 puissance 32. Si vous le calculez, vous obtenez 4294967296. Maintenant, vous avez besoin du premier bit pour décider si c'est un nombre négatif ou positif. On obtient donc 2 puissance 31 et la moitié de 4294967296, soit 2147483648. Mais zéro est un nombre positif, donc 2147483647.

183voto

OneSHOT Points 4065

Cela est dû au fait que setTimeout utilise un int de 32 bits pour stocker le délai, de sorte que la valeur maximale autorisée serait la suivante

2147483647

si vous essayez

2147483648

vous obtenez votre problème se produisant.

Je ne peux que supposer que cela provoque une forme d'exception interne dans le moteur JS et que la fonction se déclenche immédiatement au lieu de ne pas se déclencher du tout.

1 votes

Ok, ça a du sens. Je suppose que ça ne lève pas vraiment une exception interne. Au lieu de cela, je vois soit (1) un débordement d'entier, soit (2) une coercition interne du délai à une valeur int 32 bits non signée. Si (1) est le cas, alors je passe réellement une valeur négative pour le délai. Si c'est (2), alors quelque chose comme delay >>> 0 se produit, donc le délai passé est de zéro. Quoi qu'il en soit, le fait que le délai soit stocké sous la forme d'un int non signé de 32 bits explique ce comportement. Merci !

0 votes

Vieille mise à jour, mais je viens de trouver que la limite maximale est de 49999861776383 ( 49999861776384 provoque le déclenchement instantané du rappel)

11 votes

@maxp C'est parce que 49999861776383 % 2147483648 === 2147483647

34voto

Ronen Points 39

Vous pouvez utiliser :

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 votes

C'est cool, mais nous perdons la possibilité d'utiliserClearTimeout à cause de la récursion.

4 votes

Vous ne perdez pas vraiment la possibilité de l'annuler, à condition de faire vos comptes et de remplacer le timeoutId que vous voulez annuler dans cette fonction.

29voto

warpech Points 2858

Quelques explications ici : http://closure-library.googlecode.com/svn/docs/closure_goog_timer_timer.js.source.html

Les valeurs de délai d'attente trop grandes pour tenir dans un entier signé de 32 bits peuvent provoquer un débordement dans FF, Safari et Chrome, ce qui a pour conséquence que le délai d'attente est immédiatement. Il est plus logique de ne pas programmer ces délais d'attente, puisque 24,8 jours sont au-delà de ce qui est raisonnable pour que le navigateur reste ouvert.

2 votes

La réponse de warpech a beaucoup de sens - un processus long comme un serveur Node.JS peut sembler être une exception, mais pour être honnête, si vous avez quelque chose dont vous voulez vous assurer qu'il se produira dans exactement 24 jours et quelques avec une précision de l'ordre de la milliseconde, alors vous devriez utiliser quelque chose de plus robuste face aux erreurs du serveur et de la machine que setTimeout...

0 votes

@cfogelberg, je n'ai pas vu le FF ou toute autre implémentation de la setTimeout() Mais j'espère qu'ils calculent la date et l'heure auxquelles ils doivent se réveiller et qu'ils ne décrémentent pas un compteur sur un tic aléatoire... (On peut espérer, au moins)

2 votes

J'exécute Javascript en NodeJS sur un serveur, 24,8 jours c'est encore bien, mais je cherche un moyen plus logique de définir un callback pour qu'il se produise dans disons 1 mois (30 jours). Quelle serait la marche à suivre pour cela ?

8voto

SillyGilly Points 51

Consultez le document sur les minuteries ici : https://nodejs.org/api/timers.html (je suppose qu'il en va de même pour js puisque c'est un terme omniprésent dans les boucles d'événements).

En bref :

Lorsque le délai est supérieur à 2147483647 ou inférieur à 1, le délai est fixé à 1.

et le retard est :

Le nombre de millisecondes à attendre avant d'appeler le callback.

Il semble que la valeur de votre délai d'attente soit fixée par défaut à une valeur inattendue selon ces règles, peut-être ?

1voto

Tim Points 1759

Je suis tombé sur ceci lorsque j'ai essayé de déconnecter automatiquement un utilisateur dont la session a expiré. Ma solution a été de réinitialiser le délai d'expiration après un jour, et de conserver la fonctionnalité permettant d'utiliser clearTimeout.

Voici un petit exemple de prototype :

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

Utilisation :

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

Et vous pouvez l'effacer avec le stopTimer méthode :

timer.stopTimer();

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X