เหตุใด setTimeout () จึง "หยุดพัก" สำหรับค่าการหน่วงเวลามิลลิวินาทีขนาดใหญ่


104

setTimeout()ฉันมาข้ามพฤติกรรมที่ไม่คาดคิดเมื่อผ่านค่ามิลลิวินาทีขนาดใหญ่ ตัวอย่างเช่น

setTimeout(some_callback, Number.MAX_VALUE);

และ

setTimeout(some_callback, Infinity);

ทำให้ทั้งสองsome_callbackถูกเรียกใช้เกือบจะในทันทีราวกับว่าฉันผ่านไป0แทนที่จะเป็นตัวเลขจำนวนมากเมื่อเกิดความล่าช้า

ทำไมสิ่งนี้ถึงเกิดขึ้น?

คำตอบ:


143

เนื่องจาก setTimeout ใช้ int 32 บิตเพื่อจัดเก็บความล่าช้าดังนั้นค่าสูงสุดที่อนุญาตจะเป็น

2147483647

ถ้าคุณพยายาม

2147483648

คุณได้รับปัญหาที่เกิดขึ้น

ฉันสามารถสันนิษฐานได้ว่านี่เป็นสาเหตุของข้อยกเว้นภายในบางรูปแบบใน JS Engine และทำให้ฟังก์ชันเริ่มทำงานทันทีแทนที่จะไม่ทำเลย


1
เอาล่ะเข้าท่า ฉันเดาว่ามันไม่ได้เพิ่มข้อยกเว้นภายใน แต่ฉันเห็นว่ามัน (1) ทำให้เกิดจำนวนเต็มล้นหรือ (2) บังคับความล่าช้าภายในเป็นค่า int 32 บิตที่ไม่ได้ลงชื่อ ถ้าเป็นกรณี (1) แสดงว่าฉันกำลังส่งค่าลบสำหรับการหน่วงเวลา ถ้าเป็น (2) จะมีบางอย่างdelay >>> 0เกิดขึ้นดังนั้นความล่าช้าที่ส่งผ่านจึงเป็นศูนย์ ไม่ว่าจะด้วยวิธีใดก็ตามความจริงที่ว่าการหน่วงเวลาถูกจัดเก็บเป็น int ที่ไม่ได้ลงนาม 32 บิตจะอธิบายถึงพฤติกรรมนี้ ขอบคุณ!
Matt Ball

อัปเดตเก่า แต่ฉันพบว่าขีด จำกัด สูงสุดคือ49999861776383( 49999861776384ทำให้การโทรกลับเริ่มทำงานทันที)
สูงสุด

7
@maxp นั่นเป็นเพราะ49999861776383 % 2147483648 === 2147483647
David Da Silva Contín

@ DavidDaSilvaContínสายไปแล้ว แต่คุณช่วยอธิบายเพิ่มเติมได้ไหม ไม่เข้าใจว่าทำไม 2147483647 ถึงไม่ จำกัด ?
Nick Coad

2
@NickCoad ทั้งสองหมายเลขจะหน่วงเวลาเท่ากัน (เช่น 49999861776383 เหมือนกับ 2147483647 จากมุมมอง 32 บิตที่ลงชื่อ) เขียนออกมาเป็นเลขฐานสองและใช้ 31 บิตสุดท้ายทั้งหมดจะเป็น 1s
Mark Fisher

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
คุณจะไม่สูญเสียความสามารถในการยกเลิกหากคุณทำบัญชีของคุณและแทนที่ timeoutId ที่คุณต้องการยกเลิกภายในฟังก์ชันนี้
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()แต่ฉันหวังว่าพวกเขาจะคำนวณวันที่และเวลาที่ควรตื่นขึ้นมาและไม่ลดตัวนับในเห็บที่กำหนดแบบสุ่ม ... (เราสามารถหวัง อย่างน้อย)
Alexis Wilke

2
ฉันใช้ Javascript ใน NodeJS บนเซิร์ฟเวอร์ 24.8 วันก็ยังดี แต่ฉันกำลังมองหาวิธีที่สมเหตุสมผลกว่าในการตั้งค่าการโทรกลับให้เกิดขึ้นใน 1 เดือน (30 วัน) อะไรจะเป็นไปเพื่อสิ่งนี้?
พอล

1
แน่นอนว่าฉันเคยเปิดหน้าต่างเบราว์เซอร์นานกว่า 24.8 วัน เป็นเรื่องแปลกสำหรับฉันที่เบราว์เซอร์ไม่ได้ทำบางอย่างภายในเช่นโซลูชันของ Ronen อย่างน้อยก็มากถึง MAX_SAFE_INTEGER
เมื่อ

1
ใครพูด? ฉันเปิดเบราว์เซอร์ไว้นานกว่า 24 วัน ... ;)
Pete Alvin

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
Number.isInteger(foo)โดยวิธีการที่ถ้าคุณต้องการที่จะทดสอบว่าสิ่งที่เป็นจำนวนเต็มใช้ฟังก์ชัน ES6 แต่เนื่องจากยังไม่รองรับคุณสามารถใช้Math.round(foo) === fooแทนได้
Pauan

2
@ Pauan การนำไปใช้อย่างชาญฉลาดNumber.MAX_VALUEไม่ใช่จำนวนเต็ม แต่เป็นdouble. ดังนั้นจึงมีว่า ... คู่สามารถแทนจำนวนเต็มได้เนื่องจากใช้เพื่อบันทึกจำนวนเต็ม 32 บิตใน JavaScript
Alexis Wilke

1
@AlexisWilke ใช่แน่นอน JavaScript ใช้ตัวเลขทั้งหมดเป็นทศนิยม 64 บิต ถ้าตาม "จำนวนเต็ม" คุณหมายถึง "ไบนารี 32 บิต" ก็Number.MAX_VALUEไม่ใช่จำนวนเต็ม แต่ถ้าตาม "จำนวนเต็ม" คุณหมายถึงแนวคิดทางจิตของ "จำนวนเต็ม" แสดงว่าเป็นจำนวนเต็ม ใน JavaScript เนื่องจากตัวเลขทั้งหมดเป็นทศนิยม 64 บิตจึงเป็นเรื่องปกติที่จะใช้นิยามแนวคิดเชิงจิตของ "จำนวนเต็ม"
Pauan

นอกจากนี้ยังมีNumber.MAX_SAFE_INTEGERแต่นั่นไม่ใช่จำนวนที่เรากำลังมองหาที่นี่เช่นกัน
tremby
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.