Чому setTimeout () "ламається" для великих значень затримки в мілісекундах?


104

Я зіткнувся з деякою несподіваною поведінкою, передаючи велике значення мілісекунди setTimeout(). Наприклад,

setTimeout(some_callback, Number.MAX_VALUE);

і

setTimeout(some_callback, Infinity);

обидва викликають some_callbackзапуск майже відразу, як ніби я пройшов 0замість великої кількості, як затримка.

Чому це відбувається?

Відповіді:


143

Це пов'язано з тим, що setTimeout використовує 32-бітний int для зберігання затримки, щоб максимальне значення було дозволеним

2147483647

якщо ви спробуєте

2147483648

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

Я можу лише припустити, що це викликає певну форму внутрішнього виключення в двигуні JS і спричиняє функцію негайного запуску, а не взагалі.


1
Гаразд, це має сенс. Я здогадуюсь, це насправді не викликає внутрішнього винятку. Натомість я бачу, що це (1) викликає переповнення цілого числа, або (2) внутрішньо примушує затримку до непідписаного 32-бітного значення int. Якщо (1) так, то я дійсно передаю від'ємне значення затримки. Якщо це (2), то delay >>> 0відбувається щось подібне , тож прострочена затримка дорівнює нулю. Так чи інакше, той факт, що затримка зберігається як 32-бітний неподписаний int, пояснює цю поведінку. Дякую!
Метт Бал

Старе оновлення, але я щойно встановив, що максимальна межа є 49999861776383( 49999861776384викликає
негайний

7
@maxp Це тому, що49999861776383 % 2147483648 === 2147483647
David Da Silva Contín

@ DavidDaSilvaContín дуже пізно до цього, але ви можете пояснити далі? Не можете зрозуміти, чому 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
це класно, але ми втрачаємо можливість використанняClearTimeout завдяки рекурсії.
Allan Nienhuis

2
Ви дійсно не втрачаєте можливості скасувати його за умови, що ви здійснюєте бухгалтерію та замінюєте час, якщо ви хочете скасувати цю функцію.
charlag

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(), але я би сподівався, що вони обчислюють дату і час, коли він повинен прокинутися, і не декрементують лічильник на якийсь випадково визначений галочку ... (Можна сподіватися , принаймні)
Алексіс Вілке

2
Я запускаю Javascript в NodeJS на сервері, 24,8 днів все ще добре, але я шукаю більш логічний спосіб встановити зворотній дзвінок, який відбудеться через 1 місяць (30 днів). Яким був би шлях для цього?
Павло

1
У мене, звичайно, були відкриті вікна браузера довше 24,8 днів. Мені дивно, що браузери не роблять внутрішньо щось подібне до рішення Ronen, принаймні до MAX_SAFE_INTEGER
acjay

1
Хто говорить? Я тримаю браузер відкритим шляхом довше, ніж 24 дні ...;)
Піт Елвін

2

Ознайомтеся з документом вузла на Таймері тут: https://nodejs.org/api/timers.html (якщо вважати те ж, що js також є, оскільки це такий всюдисущий термін зараз на основі циклу подій

Коротко:

Якщо затримка перевищує 2147483647 або менше 1, затримка буде встановлена ​​на 1.

а затримка - це:

Кількість мілісекунд, які потрібно чекати, перш ніж викликати зворотний дзвінок.

Здається, що за вами, згідно з цими правилами, значенням тайм-ауту встановлено несподіване значення, можливо?


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замість цього.
Pauan

2
@Pauan, впровадження мудрого, Number.MAX_VALUEне ціле число, а а double. Отже, є що ... Дубляр може представляти ціле число, однак він використовується для збереження цілих чисел 32 біта в JavaScript.
Алексіс Вілке

1
@AlexisWilke Так, звичайно, JavaScript реалізує всі числа як 64-бітну плаваючу крапку. Якщо під "цілим числом" ви маєте на увазі "32-бітове двійкове", то Number.MAX_VALUEце не ціле число. Але якщо під "цілим числом" ви маєте на увазі ментальне поняття "ціле число", то це ціле число. У JavaScript, оскільки всі числа є 64-розрядною плаваючою точкою, зазвичай використовується ментальне поняття поняття "ціле число".
Паан

Також є, Number.MAX_SAFE_INTEGERале це не номер, якого ми шукаємо і тут.
тремтіння
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.