为什么setTimeout()对于较大的毫秒延迟值会“中断”?


104

将较大的毫秒值传递给时,我遇到了一些意外行为setTimeout()。例如,

setTimeout(some_callback, Number.MAX_VALUE);

setTimeout(some_callback, Infinity);

两者都导致some_callback几乎立即运行,就像我过去了一样,0而不是大量的延迟。

为什么会这样?

Answers:


143

这是由于setTimeout使用32位int存储延迟,因此允许的最大值为

2147483647

如果你试试

2147483648

您会遇到问题。

我只能假定这会导致JS Engine中某种形式的内部异常,并导致函数立即触发而不是根本不触发。


1
好的,那很有道理。我猜它实际上并没有引发内部异常。相反,我看到它要么(1)导致整数溢出,要么(2)在内部将延迟强制为无符号32位int值。如果是(1),那么我实际上将延迟传递给负值。如果为(2),则会发生类似delay >>> 0情况,因此所传递的延迟为零。无论哪种方式,延迟都存储为32位unsigned int的事实说明了这种现象。谢谢!
马特·鲍尔

旧的更新,但我刚刚发现最大限制是4999986177638349999861776384导致回调立即触发)
maxp 2014年

7
@maxp那是因为49999861776383 % 2147483648 === 2147483647
David Da SilvaContín2015年

@DavidDaSilvaContín真的很晚,但是您能进一步解释吗?不明白为什么2147483647不是限制?
尼克·科德

2
@NickCoad这两个数字将延迟相同的量(即,从带符号的32位角度来看,49999861776383与2147483647相同)。将它们写成二进制,并取最后31位,它们都将为1。
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的能力。
艾伦·尼恩

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 2014年

@cfogelberg,我还没有看到FF或FF的任何其他实现setTimeout(),但是我希望他们能计算出它应该唤醒的日期和时间,并且不要在某个随机定义的刻度上递减计数器...(一个人可以希望,至少)
Alexis Wilke 2015年

2
我正在服务器上的NodeJS中运行Javascript,仍然可以使用24.8天,但是我正在寻找一种更合理的方法来设置要在1个月(30天)内发生的回调。这样做的方式是什么?
Paul

1
我肯定已经打开浏览器窗口超过24.8天。对我来说,奇怪的是,浏览器内部没有做类似Ronen的解决方案,至少不能达到MAX_SAFE_INTEGER
大约

1
谁说?我将浏览器保持打开状态超过24天...;)
Pete Alvin

2

在此处查看有关Timers的节点文档: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毫秒,因此称为即时proc。


-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 2015年

2
@Pauan,从实现角度来看,Number.MAX_VALUE不是整数,而是a double。因此,有一个... double可以表示一个整数,因为它用于在JavaScript中保存32位整数。
亚历克西斯·威尔克

1
@AlexisWilke是的,JavaScript当然将所有数字实现为64位浮点数。如果用“整数”表示“ 32位二进制”,则Number.MAX_VALUE它不是整数。但是,如果用“整数”表示“整数”的心理概念,则它是整数。在JavaScript中,由于所有数字都是64位浮点,因此通常会使用“整数”的概念定义。
Pauan 2015年

还有,Number.MAX_SAFE_INTEGER但这也不是我们在这里寻找的号码。
tremby
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.