Dlaczego metoda setTimeout () „przerywa” przy dużych milisekundowych wartościach opóźnienia?

104

Podczas przekazywania dużej wartości w milisekundach do setTimeout(). Na przykład,

setTimeout(some_callback, Number.MAX_VALUE);

i

setTimeout(some_callback, Infinity);

oba powodują, some_callbackże są uruchamiane niemal natychmiast, jakbym przeszedł 0zamiast dużej liczby jako opóźnienie.

Dlaczego to się dzieje?

Matt Ball
źródło

Odpowiedzi:

143

Wynika to z faktu, że funkcja setTimeout używa 32-bitowej wartości int do przechowywania opóźnienia, więc maksymalna dozwolona wartość byłaby

2147483647

Jeśli spróbujesz

2147483648

masz problem.

Mogę tylko przypuszczać, że powoduje to jakąś formę wewnętrznego wyjątku w silniku JS i powoduje natychmiastowe uruchomienie funkcji, a nie wcale.

Jeden strzał
źródło
1
Dobra, to ma sens. Domyślam się, że tak naprawdę nie zgłasza wewnętrznego wyjątku. Zamiast tego widzę to albo (1) powodując przepełnienie liczby całkowitej, albo (2) wewnętrznie wymuszając opóźnienie do 32-bitowej wartości int bez znaku. Jeśli tak jest (1), to naprawdę przekazuję ujemną wartość opóźnienia. Jeśli jest (2), delay >>> 0dzieje się coś podobnego , więc przekazane opóźnienie wynosi zero. Tak czy inaczej, fakt, że opóźnienie jest przechowywane jako 32-bitowa liczba int bez znaku, wyjaśnia to zachowanie. Dzięki!
Matt Ball
Stara aktualizacja, ale właśnie znalazłem maksymalny limit 49999861776383( 49999861776384powoduje natychmiastowe wywołanie zwrotne)
maxp
7
@maxp To dlatego, że49999861776383 % 2147483648 === 2147483647
David Da Silva Contín
@ DavidDaSilvaContín naprawdę późno na to, ale czy możesz wyjaśnić dalej? Nie mogę zrozumieć, dlaczego 2147483647 nie jest limitem?
Nick Coad,
2
@NickCoad obie liczby opóźniłyby tę samą kwotę (tj. 49999861776383 to to samo co 2147483647 z 32-bitowego punktu widzenia ze znakiem). zapisz je binarnie i weź ostatnie 31 bitów, wszystkie będą 1s.
Mark Fisher
24

Możesz użyć:

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);
}
Ronen
źródło
2
to jest fajne, ale tracimy możliwość używania ClearTimeout z powodu rekurencji.
Allan Nienhuis
2
Naprawdę nie tracisz możliwości anulowania go, pod warunkiem, że prowadzisz księgowość i zastępujesz timeoutId, który chcesz anulować w tej funkcji.
charlag
23

Oto kilka wyjaśnień: http://closure-library.googlecode.com/svn/docs/closure_goog_timer_timer.js.source.html

Wartości limitu czasu zbyt duże, aby zmieścić się w 32-bitowej liczbie całkowitej ze znakiem, mogą powodować przepełnienie w FF, Safari i Chrome, powodując natychmiastowe zaplanowanie limitu czasu. Bardziej sensowne jest po prostu nie planowanie tych limitów czasu, ponieważ 24,8 dni to ponad rozsądne oczekiwanie, że przeglądarka pozostanie otwarta.

warpech
źródło
2
Odpowiedź warpech ma wiele sensu - długotrwały proces, taki jak serwer Node.JS, może brzmieć jak wyjątek, ale szczerze mówiąc, jeśli masz coś, co chcesz mieć pewność, że wydarzy się dokładnie w 24 i trochę dni z dokładnością do milisekund należy użyć czegoś bardziej wydajny w obliczu błędów serwera i urządzenia niż setTimeout ...
cfogelberg
@cfogelberg, nie widziałem FF ani żadnej innej implementacji setTimeout(), ale mam nadzieję, że obliczą datę i godzinę, kiedy ma się obudzić i nie zmniejszają licznika na jakimś losowo zdefiniowanym tiku ... (Można mieć nadzieję przynajmniej)
Alexis Wilke,
2
Uruchamiam Javascript w NodeJS na serwerze, 24,8 dni jest nadal dobre, ale szukam bardziej logicznego sposobu na ustawienie wywołania zwrotnego za powiedzmy 1 miesiąc (30 dni). Jaki byłby sposób na to?
Paul
1
Na pewno miałem otwarte okna przeglądarki dłużej niż 24,8 dnia. To dziwne dla mnie, że przeglądarki nie robią wewnętrznie czegoś takiego jak rozwiązanie Ronena, przynajmniej do MAX_SAFE_INTEGER
acjay
1
Kto mówi? Mam otwartą przeglądarkę dłużej niż 24 dni ...;)
Pete Alvin
2

Sprawdź dokumentację dotyczącą węzłów na temat timerów tutaj: https://nodejs.org/api/timers.html (zakładając to samo również w js, ponieważ jest to tak wszechobecny termin teraz w pętli zdarzeń

W skrócie:

Gdy opóźnienie jest większe niż 2147483647 lub mniejsze niż 1, opóźnienie zostanie ustawione na 1.

a opóźnienie wynosi:

Liczba milisekund oczekiwania przed wywołaniem wywołania zwrotnego.

Wygląda na to, że zgodnie z tymi regułami wartość limitu czasu jest domyślnie ustawiona na nieoczekiwaną wartość?

SillyGilly
źródło
1

Natknąłem się na to, gdy próbowałem automatycznie wylogować użytkownika z wygasłą sesją. Moim rozwiązaniem było po prostu zresetowanie limitu czasu po jednym dniu i zachowanie funkcji korzystania z clearTimeout.

Oto mały przykład prototypu:

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

Stosowanie:

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

I możesz to wyczyścić stopTimermetodą:

timer.stopTimer();
Tim
źródło
0

Nie mogę komentować, ale odpowiedzieć wszystkim ludziom. Pobiera wartość bez znaku (oczywiście nie możesz czekać ujemnych milisekund), więc ponieważ wartość maksymalna to „2147483647”, kiedy wprowadzasz wyższą wartość, zaczyna się od 0.

Zasadniczo opóźnienie = {VALUE}% 2147483647.

Więc użycie opóźnienia 2147483648 sprawiłoby, że 1 milisekunda byłby natychmiastowy.

KYGAS
źródło
-2
Number.MAX_VALUE

w rzeczywistości nie jest liczbą całkowitą. Maksymalna dopuszczalna wartość setTimeout to prawdopodobnie 2 ^ 31 lub 2 ^ 32. Próbować

parseInt(Number.MAX_VALUE) 

i otrzymujesz 1 z powrotem zamiast 1,7976931348623157e + 308.

Osmund
źródło
13
To jest nieprawidłowe: Number.MAX_VALUEjest liczbą całkowitą. Jest to liczba całkowita 17976931348623157 z 292 zerami po niej. Przyczyną parseIntpowrotu 1jest to, że najpierw konwertuje swój argument na łańcuch, a następnie przeszukuje ciąg od lewej do prawej. Gdy tylko znajdzie .(który nie jest liczbą), zatrzymuje się.
Pauan
1
Przy okazji, jeśli chcesz sprawdzić, czy coś jest liczbą całkowitą, użyj funkcji ES6 Number.isInteger(foo). Ale ponieważ nie jest jeszcze obsługiwany, możesz użyć Math.round(foo) === foozamiast tego.
Pauan
2
@Pauan, jeśli chodzi o implementację, Number.MAX_VALUEnie jest liczbą całkowitą, ale double. Tak więc ... Podwójna może reprezentować liczbę całkowitą, ponieważ jest używana do zapisywania liczb całkowitych 32-bitowych w JavaScript.
Alexis Wilke
1
@AlexisWilke Tak, oczywiście JavaScript implementuje wszystkie liczby jako 64-bitowe liczby zmiennoprzecinkowe. Jeśli przez „liczbę całkowitą” masz na myśli „32-bitowy plik binarny”, Number.MAX_VALUEto nie jest liczbą całkowitą. Ale jeśli przez „liczbę całkowitą” masz na myśli koncepcję „liczby całkowitej”, to jest to liczba całkowita. W JavaScript, ponieważ wszystkie liczby są 64-bitowymi liczbami zmiennoprzecinkowymi, często używa się mentalnej definicji „liczby całkowitej”.
Pauan
Jest też, Number.MAX_SAFE_INTEGERale nie jest to liczba, której tutaj szukamy.
tremby