Почему setTimeout () «прерывается» при больших значениях задержки в миллисекундах?

104

Я столкнулся с неожиданным поведением при передаче большого значения миллисекунды в setTimeout(). Например,

setTimeout(some_callback, Number.MAX_VALUE);

и

setTimeout(some_callback, Infinity);

оба заставляют some_callbackзапускаться почти сразу, как если бы я пропустил 0вместо большого числа в качестве задержки.

Почему это происходит?

Мэтт Болл
источник

Ответы:

143

Это связано с тем, что setTimeout использует 32-битное int для хранения задержки, поэтому максимальное допустимое значение будет

2147483647

если вы попытаетесь

2147483648

у вас возникает проблема.

Я могу только предположить, что это вызывает некоторую форму внутреннего исключения в JS Engine и приводит к немедленному срабатыванию функции, а не вовсе.

Один выстрел
источник
1
Ладно, в этом есть смысл. Я предполагаю, что на самом деле это не вызывает внутреннего исключения. Вместо этого я вижу, что это либо (1) вызывает переполнение целого числа, либо (2) внутренне приводит задержку к беззнаковому 32-битному значению int. В случае (1) я действительно передаю отрицательное значение задержки. Если это (2), то delay >>> 0происходит что-то вроде этого , поэтому пройденная задержка равна нулю. В любом случае, это поведение объясняется тем фактом, что задержка хранится как 32-битное целое число без знака. Спасибо!
Мэтт Болл,
Старое обновление, но я только что обнаружил, что максимальный предел 49999861776383( 49999861776384вызывает мгновенное
срабатывание
7
@maxp Это потому, что49999861776383 % 2147483648 === 2147483647
Дэвид Да Силва Контин
@ DavidDaSilvaContíНа самом деле поздно, но не могли бы вы объяснить дальше? Не можете понять, почему 2147483647 - это не предел?
Ник Коуд
2
@NickCoad: оба числа задерживают одинаковую сумму (т.е. 49999861776383 совпадает с 2147483647 с 32-битной точки зрения со знаком). запишите их в двоичном формате и возьмите последний 31 бит, все они будут равны 1.
Марк Фишер
24

Ты можешь использовать:

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
это круто, но мы теряем возможность useClearTimeout из-за рекурсии.
Allan Nienhuis
2
Вы действительно не теряете возможность отменить его при условии, что вы ведете учет и заменяете timeoutId, который хотите отменить внутри этой функции.
charlag 02
23

Некоторое объяснение здесь: http://closure-library.googlecode.com/svn/docs/closure_goog_timer_timer.js.source.html

Значения тайм-аута, слишком большие, чтобы поместиться в 32-битное целое число со знаком, может вызвать переполнение в FF, Safari и Chrome, что приведет к немедленному планированию тайм-аута. Имеет смысл просто не планировать эти тайм-ауты, поскольку 24,8 дня превышают разумные ожидания, чтобы браузер оставался открытым.

варпеч
источник
2
Ответ warpech имеет большой смысл - длительный процесс, такой как сервер Node.JS, может звучать как исключение, но, честно говоря, если у вас есть что-то, что вы хотите гарантировать, произойдет ровно через 24 с небольшим дня с точностью до миллисекунды тогда вам следует использовать что-то более надежное перед лицом ошибок сервера и машины, чем setTimeout ...
cfogelberg
@cfogelberg, я не видел FF или какой-либо другой реализации setTimeout(), но я надеюсь, что они вычисляют дату и время, когда он должен проснуться, и не уменьшают счетчик на каком-то случайно заданном тике ... (Можно надеяться , по крайней мере)
Alexis Wilke
2
Я использую Javascript в NodeJS на сервере, 24,8 дня все еще хорошо, но я ищу более логичный способ настроить обратный вызов, который произойдет, скажем, через 1 месяц (30 дней). Что для этого нужно сделать?
Пол
1
У меня наверняка окна браузера открывались дольше 24,8 дней. Для меня странно, что браузеры внутренне не делают что-то вроде решения Ронена, по крайней мере, до MAX_SAFE_INTEGER
acjay
1
Кто говорит? Я держу свой браузер открытым дольше 24 дней ...;)
Пит Элвин
2

Ознакомьтесь с документацией узла по таймерам здесь: https://nodejs.org/api/timers.html (предполагается, что то же самое и для js, поскольку теперь это такой повсеместный термин в цикле событий, основанном на

Коротко:

Если задержка больше 2147483647 или меньше 1, задержка будет установлена ​​на 1.

и задержка:

Количество миллисекунд ожидания перед вызовом обратного вызова.

Похоже, что для вашего тайм-аута по умолчанию установлено неожиданное значение в соответствии с этими правилами, возможно?

SillyGilly
источник
1

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

Вот небольшой пример прототипа:

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

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

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

И вы можете очистить это с помощью stopTimerметода:

timer.stopTimer();
Тим
источник
0

Не могу комментировать, но отвечать всем людям. Он принимает беззнаковое значение (очевидно, вы не можете ждать отрицательные миллисекунды). Так как максимальное значение равно «2147483647», когда вы вводите более высокое значение, оно начинается с 0.

Обычно задержка = {VALUE}% 2147483647.

Таким образом, использование задержки 2147483648 сделает его 1 миллисекундой, следовательно, мгновенный процесс.

КЫГАС
источник
-2
Number.MAX_VALUE

на самом деле не целое число. Максимально допустимое значение для setTimeout, скорее всего, 2 ^ 31 или 2 ^ 32. Пытаться

parseInt(Number.MAX_VALUE) 

и вы получите 1 обратно вместо 1,7976931348623157e + 308.

Осмунд
источник
13
Это неверно: Number.MAX_VALUEэто целое число. Это целое число 17976931348623157 с 292 нулями после. Причина parseIntвозврата в 1том, что он сначала преобразует свой аргумент в строку, а затем выполняет поиск в строке слева направо. Как только он находит .(а это не число), он останавливается.
Pauan
1
Кстати, если вы хотите проверить, является ли что-то целым числом, используйте функцию ES6 Number.isInteger(foo). Но поскольку он еще не поддерживается, вы можете использовать Math.round(foo) === fooвместо него.
Пауан
2
@Pauan, с точки зрения реализации, Number.MAX_VALUEне целое число, а double. Итак, вот что ... Двойное число может представлять целое число, поскольку оно используется для сохранения 32-битных целых чисел в JavaScript.
Alexis Wilke
1
@AlexisWilke Да, конечно, JavaScript реализует все числа как 64-битные числа с плавающей запятой. Если под «целым числом» вы подразумеваете «32-битный двоичный код», то Number.MAX_VALUEэто не целое число. Но если под «целым числом» вы подразумеваете мысленное понятие «целое число», то это целое число. В JavaScript, поскольку все числа представляют собой 64-битные числа с плавающей запятой, обычно используется ментальное определение понятия «целое число».
Пауан
Также есть, Number.MAX_SAFE_INTEGERно это не тот номер, который мы здесь ищем.
tremby