SetTimeout () büyük milisaniye gecikme değerleri için neden "kesiliyor"?


104

'A büyük bir milisaniye değerini iletirken bazı beklenmedik davranışlarla karşılaştım setTimeout(). Örneğin,

setTimeout(some_callback, Number.MAX_VALUE);

ve

setTimeout(some_callback, Infinity);

her ikisi de gecikme olarak çok sayıda yerine some_callbackgeçmişim gibi, neredeyse anında çalıştırılmasına neden oluyor 0.

Bu neden oluyor?

Yanıtlar:


143

Bunun nedeni, gecikmeyi saklamak için 32 bitlik bir int kullanan setTimeout'dur, böylece izin verilen maksimum değer

2147483647

Eğer denersen

2147483648

problemin oluşmasını sağlıyorsun.

Bunun JS Motorunda bir tür iç istisnaya neden olduğunu ve işlevin hiç olmamak yerine hemen çalışmasına neden olduğunu varsayabilirim.


1
Tamam, bu mantıklı. Sanırım aslında dahili bir istisna oluşturmuyor. Bunun yerine, ya (1) bir tamsayı taşmasına neden olduğunu ya da (2) gecikmeyi işaretsiz 32 bit int değerine dahili olarak zorladığını görüyorum. Durum (1) ise, gecikme için gerçekten negatif bir değer geçiyorum. Eğer (2) ise, o zaman benzer bir şey delay >>> 0olur, yani geçen gecikme sıfırdır. Her iki durumda da gecikmenin 32 bitlik işaretsiz int olarak saklanması bu davranışı açıklar. Teşekkürler!
Matt Ball

Eski güncelleme, ancak az önce maksimum sınırın 49999861776383( 49999861776384geri
aramanın

7
@maxp Çünkü49999861776383 % 2147483648 === 2147483647
David Da Silva Contín

@ DavidDaSilvaContín buna gerçekten geç kaldı, ancak daha fazla açıklayabilir misin? Neden 2147483647'nin sınır olmadığını anlayamıyor musunuz?
Nick Coad

2
@NickCoad her iki numara da aynı miktarı geciktirir (yani 49999861776383, imzalı 32 bitlik bakış açısından 2147483647 ile aynıdır). bunları ikili olarak yazın ve son 31 biti alın, hepsi 1 olacak.
Mark Fisher

24

Kullanabilirsiniz:

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
bu harika, ancak özyineleme nedeniyleClearTimeout kullanma yeteneğimizi kaybediyoruz.
Allan Nienhuis

2
Defter tutmayı yapmanız ve bu işlev içinde iptal etmek istediğiniz zaman aşımı kimliğini değiştirmeniz koşuluyla, gerçekten iptal etme yeteneğini kaybetmezsiniz.
charlag

23

Burada bazı açıklamalar: http://closure-library.googlecode.com/svn/docs/closure_goog_timer_timer.js.source.html

İmzalı bir 32 bit tam sayıya sığmayacak kadar büyük zaman aşımı değerleri FF, Safari ve Chrome'da taşmaya neden olabilir ve zaman aşımının hemen planlanmasına neden olabilir. Tarayıcının açık kalması için 24,8 gün makul bir beklentinin ötesinde olduğundan, bu zaman aşımlarını planlamamak daha mantıklıdır.


2
warpech'in cevabı çok mantıklı - bir Node.JS sunucusu gibi uzun süren bir süreç bir istisna gibi görünebilir, ancak dürüst olmak gerekirse, milisaniye hassasiyetinde tam olarak 24 ve birkaç gün içinde olmasını istediğiniz bir şeyiniz varsa o zaman sunucu ve makine hataları karşısında setTimeout'tan daha sağlam bir şey kullanmalısınız ...
cfogelberg

@cfogelberg, FF'yi veya bunun başka bir uygulamasını görmedim setTimeout(), ancak umarım uyanması gereken tarih ve saati hesaplarlar ve rastgele tanımlanmış bir kene üzerinde bir sayacı düşürmezler ... , en azından)
Alexis Wilke

2
Javascript'i bir sunucuda NodeJS'de çalıştırıyorum, 24.8 gün hala iyi, ancak 1 ay (30 gün) içinde bir geri aramayı ayarlamak için daha mantıklı bir yol arıyorum. Bunun için gitmenin yolu ne olabilir?
Paul

1
24,8 günden daha uzun süredir açık tarayıcı pencerelerim var. Tarayıcıların dahili olarak Ronen'in çözümü gibi bir şey yapmaması bana tuhaf geliyor, en azından MAX_SAFE_INTEGER
acjay

1
Kim demiş? Tarayıcımı 24 günden daha uzun süre açık
Pete Alvin

2

Zamanlayıcılarla ilgili düğüm belgesine buradan göz atın: https://nodejs.org/api/timers.html (js'de de aynısını varsayarsak, artık olay döngüsü tabanlı olarak çok yaygın bir terimdir.

Kısacası:

Gecikme 2147483647'den büyük veya 1'den az olduğunda, gecikme 1 olarak ayarlanacaktır.

ve gecikme:

Geri aramayı aramadan önce beklenecek milisaniye sayısı.

Zaman aşımı değeriniz bu kurallara göre beklenmeyen bir değere varsayılan olarak ayarlanmış gibi görünüyor, muhtemelen?


1

Süresi dolmuş bir oturumu olan bir kullanıcıyı otomatik olarak kapatmaya çalıştığımda buna rastladım. Çözümüm, bir gün sonra zaman aşımını sıfırlamak ve clearTimeout'u kullanmak için işlevselliği korumaktı.

İşte küçük bir prototip örneği:

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

Kullanım:

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

Ve bunu şu yöntemle temizleyebilirsiniz stopTimer:

timer.stopTimer();

0

Yorum yapamıyorum ama tüm insanlara cevap vermek. İşaretsiz bir değer alır (tabii ki negatif milisaniye bekleyemezsiniz) Yani maksimum değer "2147483647" olduğundan daha yüksek bir değer girdiğinizde 0'dan gitmeye başlar.

Temel olarak gecikme = {VALUE}% 2147483647.

Yani 2147483648 gecikme kullanmak 1 milisaniye yapar, bu nedenle anlık proc.


-2
Number.MAX_VALUE

aslında bir tamsayı değil. SetTimeout için izin verilen maksimum değer muhtemelen 2 ^ 31 veya 2 ^ 32'dir. Deneyin

parseInt(Number.MAX_VALUE) 

ve 1.7976931348623157e + 308 yerine 1 geri alırsınız.


13
Bu yanlış: Number.MAX_VALUEbir tam sayıdır. 17976931348623157 tamsayıdır ve sonrasında 292 sıfırdır. Sebebi parseIntdöner 1öncelikle bir dizeye argümanını dönüştürür ve sonra soldan sağa dize arar çünkü. .(Sayı olmayan) bulduğu anda durur.
Pauan

1
Bu arada, bir şeyin tam sayı olup olmadığını test etmek istiyorsanız, ES6 işlevini kullanın Number.isInteger(foo). Ancak henüz desteklenmediği için Math.round(foo) === foobunun yerine kullanabilirsiniz .
Pauan

2
@Pauan, uygulama açısından Number.MAX_VALUEbir tamsayı değil a double. Yani şu var ... Bir double bir tamsayıyı temsil edebilir, çünkü JavaScript'te 32 bitlik tamsayıları kaydetmek için kullanılır.
Alexis Wilke

1
@AlexisWilke Evet, elbette JavaScript tüm sayıları 64-bit kayan nokta olarak uygular . Eğer "tamsayı" ile "32-bit ikili" demek istiyorsan o zaman Number.MAX_VALUEbir tamsayı değil. Ama "tamsayı" derken zihinsel "bir tam sayı" kavramını kastediyorsanız, o zaman bu bir tam sayıdır. JavaScript'te, tüm sayılar 64-bit kayan nokta olduğundan, "tamsayı" nın zihinsel kavram tanımının kullanılması yaygındır.
Pauan

Bir de var Number.MAX_SAFE_INTEGERama burada aradığımız numara bu değil.
tremby
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.