Tại sao setTimeout () "ngắt" đối với các giá trị độ trễ mili giây lớn?


104

Tôi đã gặp phải một số hành vi không mong muốn khi chuyển một giá trị mili giây lớn tới setTimeout(). Ví dụ,

setTimeout(some_callback, Number.MAX_VALUE);

setTimeout(some_callback, Infinity);

cả hai nguyên nhân some_callbackđể được chạy gần như ngay lập tức, như thể tôi đã vượt qua 0thay vì một số lớn như sự chậm trễ.

Lý do tại sao điều này xảy ra?

Câu trả lời:


143

Điều này là do setTimeout sử dụng int 32 bit để lưu trữ độ trễ, vì vậy giá trị tối đa cho phép sẽ là

2147483647

nếu bạn cố gắng

2147483648

bạn nhận được vấn đề của bạn đang xảy ra.

Tôi chỉ có thể cho rằng điều này đang gây ra một số dạng ngoại lệ nội bộ trong JS Engine và khiến hàm kích hoạt ngay lập tức chứ không phải là không.


1
Được rồi, điều đó có ý nghĩa. Tôi đoán nó không thực sự đưa ra một ngoại lệ nội bộ. Thay vào đó, tôi thấy nó (1) gây tràn số nguyên hoặc (2) nội bộ ép độ trễ thành giá trị int 32 bit không dấu. Nếu (1) là trường hợp, thì tôi thực sự đang chuyển một giá trị âm cho độ trễ. Nếu là (2), thì điều gì đó tương tự delay >>> 0sẽ xảy ra, vì vậy độ trễ đã qua bằng không. Dù bằng cách nào, thực tế là độ trễ được lưu trữ dưới dạng int 32-bit không dấu giải thích hành vi này. Cảm ơn!
Matt Ball

Cũ cập nhật, nhưng tôi vừa tìm thấy các giới hạn tối đa là 49999861776383( 49999861776384gây ra gọi lại để lửa ngay lập tức)
maxp

7
@maxp Đó là bởi vì49999861776383 % 2147483648 === 2147483647
David Da Silva Contín

@ DavidDaSilvaContín thực sự muộn với điều này, nhưng bạn có thể giải thích thêm không? Không thể hiểu tại sao 2147483647 không phải là giới hạn?
Nick Coad

2
@NickCoad cả hai số sẽ trì hoãn cùng một số lượng (tức là 49999861776383 giống với 2147483647 theo quan điểm 32 bit có dấu). viết chúng ra dưới dạng nhị phân và lấy 31 bit cuối cùng, tất cả chúng sẽ là 1s.
Mark Fisher

24

Bạn có thể dùng:

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
điều này thật tuyệt, nhưng chúng tôi đã mất khả năng sử dụngClearTimeout do đệ quy.
Allan Nienhuis

2
Bạn không thực sự mất khả năng hủy nó với điều kiện là bạn thực hiện việc ghi sổ kế toán và thay thế timeoutId bạn muốn hủy bên trong chức năng này.
charlag

23

Một số giải thích ở đây: http://closure-library.googlecode.com/svn/docs/closure_goog_timer_timer.js.source.html

Giá trị thời gian chờ quá lớn để vừa với số nguyên 32-bit có dấu có thể gây tràn trong FF, Safari và Chrome, dẫn đến việc hết thời gian được lập lịch ngay lập tức. Đơn giản là không lên lịch cho những khoảng thời gian chờ này sẽ có ý nghĩa hơn, vì 24,8 ngày nằm ngoài dự kiến ​​hợp lý để trình duyệt luôn mở.


2
Câu trả lời của warpech có rất nhiều ý nghĩa - một quá trình chạy dài như máy chủ Node.JS nghe có vẻ như là một ngoại lệ, nhưng thành thật mà nói nếu bạn có thứ gì đó mà bạn muốn đảm bảo sẽ xảy ra chính xác trong 24 ngày và một chút với độ chính xác mili giây sau đó bạn nên sử dụng một cái gì đó mạnh mẽ hơn khi đối mặt với máy chủ và máy lỗi hơn setTimeout ...
cfogelberg

@cfogelberg, tôi chưa thấy FF hoặc bất kỳ triển khai nào khác của setTimeout(), nhưng tôi hy vọng rằng họ tính toán ngày và giờ khi nó thức dậy và không làm giảm bộ đếm trên một số đánh dấu được xác định ngẫu nhiên ... (Ai đó có thể hy vọng , ít nhất)
Alexis Wilke

2
Tôi đang chạy Javascript trong NodeJS trên một máy chủ, 24,8 ngày vẫn tốt, nhưng tôi đang tìm một cách hợp lý hơn để đặt lệnh gọi lại sẽ xảy ra trong vòng 1 tháng (30 ngày). Đâu sẽ là cách để đi cho điều này?
Paul

1
Tôi chắc chắn đã mở cửa sổ trình duyệt lâu hơn 24,8 ngày. Đó là kỳ lạ với tôi rằng trình duyệt không nội làm điều gì đó giống như giải pháp Ronen của, ít nhất lên đến MAX_SAFE_INTEGER
acjay

1
Ai nói? Tôi giữ cho trình duyệt của mình mở lâu hơn 24 ngày ...;)
Pete Alvin

2

Kiểm tra tài liệu nút trên Timers tại đây: https://nodejs.org/api/timers.html (giả sử giống nhau trên js vì nó là một thuật ngữ phổ biến hiện nay trong vòng lặp sự kiện

Nói ngắn gọn:

Khi độ trễ lớn hơn 2147483647 hoặc nhỏ hơn 1, độ trễ sẽ được đặt thành 1.

và độ trễ là:

Số mili giây phải chờ trước khi gọi lại.

Có vẻ như giá trị thời gian chờ của bạn đang được mặc định thành một giá trị không mong muốn theo các quy tắc này, có thể không?


1

Tôi đã vấp phải điều này khi cố gắng tự động đăng xuất một người dùng có phiên hết hạn. Giải pháp của tôi là chỉ đặt lại thời gian chờ sau một ngày và giữ chức năng sử dụng clearTimeout.

Đây là một ví dụ nguyên mẫu nhỏ:

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

Sử dụng:

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

Và bạn có thể xóa nó bằng stopTimerphương pháp:

timer.stopTimer();

0

Không thể bình luận nhưng để trả lời tất cả mọi người. Nó nhận giá trị không dấu (hiển nhiên là bạn không thể đợi mili giây âm) Vì vậy, vì giá trị tối đa là "2147483647" khi bạn nhập giá trị cao hơn, nó sẽ bắt đầu từ 0.

Về cơ bản độ trễ = {VALUE}% 2147483647.

Vì vậy, sử dụng độ trễ của 2147483648 sẽ làm cho nó trở thành 1 mili giây, do đó, proc tức thì.


-2
Number.MAX_VALUE

thực sự không phải là một số nguyên. Giá trị tối đa cho phép cho setTimeout có thể là 2 ^ 31 hoặc 2 ^ 32. Thử

parseInt(Number.MAX_VALUE) 

và bạn nhận lại 1 thay vì 1.7976931348623157e + 308.


13
Điều này không chính xác: Number.MAX_VALUElà một số nguyên. Nó là số nguyên 17976931348623157 với 292 số không sau đó. Lý do parseInttrả về 1là vì trước tiên nó chuyển đổi đối số của nó thành một chuỗi và sau đó tìm kiếm chuỗi từ trái sang phải. Ngay sau khi nó tìm thấy .(không phải là số), nó sẽ dừng lại.
Pauan

1
Nhân tiện, nếu bạn muốn kiểm tra xem thứ gì đó có phải là số nguyên hay không, hãy sử dụng hàm ES6 Number.isInteger(foo). Nhưng vì nó chưa được hỗ trợ nên bạn có thể sử dụng Math.round(foo) === foothay thế.
Pauan

2
@Pauan, triển khai khôn ngoan, Number.MAX_VALUEkhông phải là một số nguyên mà là một double. Vì vậy, có điều đó ... Tuy nhiên, một đôi có thể đại diện cho một số nguyên, vì nó được sử dụng để lưu các số nguyên 32 bit trong JavaScript.
Alexis Wilke

1
@AlexisWilke Có, tất nhiên JavaScript thực hiện tất cả các số dưới dạng dấu phẩy động 64-bit. Nếu theo "số nguyên", bạn có nghĩa là "nhị phân 32-bit" thì Number.MAX_VALUEkhông phải là số nguyên. Nhưng nếu theo "số nguyên", bạn có nghĩa là khái niệm tinh thần của "một số nguyên", thì nó là một số nguyên. Trong JavaScript, bởi vì tất cả các số đều là dấu phẩy động 64-bit, nên người ta thường sử dụng định nghĩa khái niệm "số nguyên".
Pauan

Cũng có Number.MAX_SAFE_INTEGERnhưng đó không phải là con số chúng tôi đang tìm kiếm ở đây.
run rẩy
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.