Mengapa setTimeout () "istirahat" untuk nilai penundaan milidetik yang besar?

104

Saya menemukan beberapa perilaku yang tidak terduga saat meneruskan nilai milidetik yang besar ke setTimeout(). Misalnya,

setTimeout(some_callback, Number.MAX_VALUE);

dan

setTimeout(some_callback, Infinity);

keduanya menyebabkan some_callbackuntuk dijalankan hampir seketika, seolah-olah saya telah lulus 0bukannya sejumlah besar sebagai penundaan.

Mengapa ini terjadi?

Matt Ball
sumber

Jawaban:

143

Ini karena setTimeout menggunakan int 32 bit untuk menyimpan penundaan sehingga nilai maksimal yang diizinkan

2147483647

jika kamu mencoba

2147483648

Anda mendapatkan masalah Anda terjadi.

Saya hanya dapat berasumsi bahwa ini menyebabkan beberapa bentuk pengecualian internal di JS Engine dan menyebabkan fungsi langsung aktif daripada tidak sama sekali.

Satu tembakan
sumber
1
Oke, itu masuk akal. Saya menduga itu tidak benar-benar menimbulkan pengecualian internal. Sebaliknya, saya melihatnya (1) menyebabkan overflow integer, atau (2) secara internal memaksa penundaan ke nilai int 32-bit unsigned. Jika (1) adalah kasusnya, maka saya benar-benar memberikan nilai negatif untuk keterlambatan. Jika (2), maka delay >>> 0terjadi sesuatu seperti , jadi penundaan yang dilewati adalah nol. Bagaimanapun, fakta bahwa penundaan disimpan sebagai 32-bit unsigned int menjelaskan perilaku ini. Terima kasih!
Matt Ball
Update tua, tetapi saya baru saja menemukan max limit adalah 49999861776383( 49999861776384menyebabkan callback untuk api langsung)
maxp
7
@maxp Itu karena49999861776383 % 2147483648 === 2147483647
David Da Silva Contín
@ DavidDaSilvaContín sangat terlambat untuk ini, tetapi dapatkah Anda menjelaskan lebih lanjut? Tidak mengerti mengapa 2147483647 bukan batasnya?
Nick Coad
2
@NickCoad kedua nomor akan menunda jumlah yang sama (yaitu 49999861776383 sama dengan 2147483647 dari sudut pandang 32 bit yang ditandatangani). tulislah dalam biner, dan ambil 31 bit terakhir, semuanya akan menjadi 1.
Mark Fisher
24

Kamu bisa memakai:

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
sumber
2
ini keren, tapi kami kehilangan kemampuan untuk useClearTimeout karena rekursi.
Allan Nienhuis
2
Anda tidak benar-benar kehilangan kemampuan untuk membatalkannya asalkan Anda melakukan pembukuan dan mengganti timeoutId yang ingin Anda batalkan di dalam fungsi ini.
charlag
23

Beberapa penjelasan di sini: http://closure-library.googlecode.com/svn/docs/closure_goog_timer_timer.js.source.html

Nilai waktu tunggu yang terlalu besar untuk dimasukkan ke dalam bilangan bulat 32-bit yang ditandatangani dapat menyebabkan luapan di FF, Safari, dan Chrome, sehingga waktu tunggu akan segera dijadwalkan. Lebih masuk akal untuk tidak menjadwalkan waktu tunggu ini, karena 24,8 hari adalah di luar ekspektasi yang wajar agar browser tetap terbuka.

warpech
sumber
2
Jawaban warpech sangat masuk akal - proses yang berjalan lama seperti server Node.JS mungkin terdengar seperti pengecualian, tetapi jujur ​​saja jika Anda memiliki sesuatu yang ingin Anda pastikan terjadi tepat dalam 24 dan beberapa hari dengan akurasi milidetik maka Anda harus menggunakan sesuatu yang lebih kuat dalam menghadapi kesalahan server dan mesin daripada setTimeout ...
cfogelberg
@cfogelberg, saya belum melihat FF atau implementasi lainnya dari setTimeout(), tetapi saya berharap mereka menghitung tanggal dan waktu ketika harus bangun dan tidak mengurangi penghitung pada beberapa centang yang ditentukan secara acak ... (Orang bisa berharap , setidaknya)
Alexis Wilke
2
Saya menjalankan Javascript di NodeJS pada server, 24,8 hari masih bagus, tetapi saya mencari cara yang lebih logis untuk mengatur panggilan balik agar terjadi dalam waktu 1 bulan (30 hari). Apa cara untuk melakukannya?
Paul
1
Saya pasti pernah membuka jendela browser lebih dari 24,8 hari. Aneh bagi saya bahwa browser tidak secara internal melakukan sesuatu seperti solusi Ronen, setidaknya hingga MAX_SAFE_INTEGER
acjay
1
Siapa bilang? Saya tetap membuka browser saya lebih dari 24 hari ...;)
Pete Alvin
2

Lihat dokumen node di Timer di sini: https://nodejs.org/api/timers.html (dengan asumsi yang sama di js juga karena itu istilah yang ada di mana-mana sekarang dalam event loop based

Pendeknya:

Jika penundaan lebih besar dari 2147483647 atau kurang dari 1, penundaan akan disetel ke 1.

dan penundaan adalah:

Jumlah milidetik untuk menunggu sebelum memanggil callback.

Sepertinya nilai waktu tunggu Anda disetel secara default ke nilai yang tidak terduga sesuai aturan ini, mungkin?

SillyGilly
sumber
1

Saya tersandung pada ini ketika saya mencoba untuk mengeluarkan pengguna secara otomatis dengan sesi yang kedaluwarsa. Solusi saya adalah mengatur ulang waktu tunggu setelah satu hari, dan tetap menggunakan fungsionalitas untuk menggunakan clearTimeout.

Berikut adalah contoh prototipe kecil:

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

Pemakaian:

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

Dan Anda dapat menghapusnya dengan stopTimermetode:

timer.stopTimer();
Tim
sumber
0

Tidak bisa berkomentar selain menjawab semua orang. Dibutuhkan nilai unsigned (Anda tidak bisa menunggu milidetik negatif jelas) Jadi karena nilai maks adalah "2147483647" ketika Anda memasukkan nilai yang lebih tinggi itu mulai pergi dari 0.

Pada dasarnya penundaan = {VALUE}% 2147483647.

Jadi menggunakan penundaan 2147483648 akan menjadikannya 1 milidetik, oleh karena itu, proses instan.

KYGAS
sumber
-2
Number.MAX_VALUE

sebenarnya bukan bilangan bulat. Nilai maksimum yang diizinkan untuk setTimeout kemungkinan adalah 2 ^ 31 atau 2 ^ 32. Mencoba

parseInt(Number.MAX_VALUE) 

dan Anda mendapatkan 1 kembali, bukan 1.7976931348623157e + 308.

Osmund
sumber
13
Ini salah: Number.MAX_VALUEadalah bilangan bulat. Ini adalah bilangan bulat 17976931348623157 dengan 292 angka nol setelahnya. Alasan parseIntpengembalian 1adalah karena pertama kali mengubah argumennya menjadi string dan kemudian mencari string dari kiri ke kanan. Segera setelah menemukan .(yang bukan angka), ia berhenti.
Pauan
1
Omong-omong, jika Anda ingin menguji apakah sesuatu adalah bilangan bulat, gunakan fungsi ES6 Number.isInteger(foo). Tetapi karena belum didukung, Anda dapat menggunakan Math.round(foo) === foosebagai gantinya.
Pauan
2
@Pauan, implementasi bijaksana, Number.MAX_VALUEbukanlah integer tapi a double. Jadi begitulah ... Double bisa mewakili integer, karena digunakan untuk menyimpan integer 32 bit di JavaScript.
Alexis Wilke
1
@AlexisWilke Ya, tentu saja JavaScript menerapkan semua angka sebagai titik mengambang 64-bit. Jika yang Anda maksud dengan "integer" adalah "32-bit binary" maka Number.MAX_VALUEitu bukan integer. Tetapi jika yang Anda maksud dengan "integer" adalah konsep mental "sebuah integer", maka itu adalah integer. Dalam JavaScript, karena semua bilangan adalah floating point 64-bit, maka umum untuk menggunakan definisi konsep mental "integer."
Pauan
Ada juga Number.MAX_SAFE_INTEGERtapi itu bukan nomor yang kita cari di sini.
gemetar