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

IT技术 javascript settimeout
2021-02-05 23:19:22

在将大毫秒值传递给setTimeout(). 例如,

setTimeout(some_callback, Number.MAX_VALUE);

setTimeout(some_callback, Infinity);

两者都导致some_callback几乎立即运行,就好像我已经通过0而不是大量作为延迟。

为什么会发生这种情况?

6个回答

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

2147483647

如果你试试

2147483648

你的问题发生了。

我只能假设这是在 JS 引擎中导致某种形式的内部异常并导致函数立即触发而不是根本不触发。

好吧,这是有道理的。我猜它实际上并没有引发内部异常。相反,我看到它要么 (1) 导致整数溢出,要么 (2) 在内部将延迟强制为无符号 32 位 int 值。如果 (1) 是这种情况,那么我真的为延迟传递了一个负值。如果是 (2),那么delay >>> 0就会发生类似的事情,因此传递的延迟为零。无论哪种方式,延迟存储为 32 位无符号整数这一事实解释了这种行为。谢谢!
2021-03-19 23:19:22
@DavidDaSilvaContín 真的很晚了,但你能进一步解释一下吗?不明白为什么 2147483647 不是极限?
2021-03-23 23:19:22
@NickCoad 两个数字都会延迟相同的数量(即,从有符号的 32 位角度来看,49999861776383 与 2147483647 相同)。用二进制写出来,取最后31位,全为1。
2021-03-27 23:19:22
@maxp 那是因为 49999861776383 % 2147483648 === 2147483647
2021-03-29 23:19:22
旧更新,但我刚刚发现最大限制是4999986177638349999861776384导致回调立即触发)
2021-04-03 23:19:22

您可以使用:

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);
}
这很酷,但由于递归,我们失去了 useClearTimeout 的能力。
2021-03-20 23:19:22
只要您进行簿记并替换要在此函数中取消的 timeoutId,您就不会真正失去取消它的能力。
2021-04-11 23:19:22

这里有一些解释:http : //closure-library.googlecode.com/svn/docs/closure_goog_timer_timer.js.source.html

超时值太大而无法放入有符号的 32 位整数可能会导致 FF、Safari 和 Chrome 中的溢出,从而导致立即安排超时。不安排这些超时更有意义,因为 24.8 天超出了浏览器保持打开状态的合理预期。

谁说?我的浏览器打开时间超过 24 天...;)
2021-03-19 23:19:22
当然,我的浏览器窗口打开时间超过 24.8 天。对我来说很奇怪,浏览器在内部并没有像 Ronen 的解决方案那样做一些事情,至少达到 MAX_SAFE_INTEGER
2021-03-21 23:19:22
我在服务器上的 NodeJS 中运行 Javascript,24.8 天还是不错的,但我正在寻找一种更合乎逻辑的方法来设置回调在 1 个月(30 天)内发生。这样做的方法是什么?
2021-03-25 23:19:22
@cfogelberg,我还没有看到 FF 或任何其他实现setTimeout(),但我希望他们计算它应该醒来的日期和时间,并且不要在一些随机定义的滴答声上递减计数器......(人们可以希望, 至少)
2021-04-08 23:19:22
warpech 的回答很有道理——像 Node.JS 服务器这样的长时间运行的进程可能听起来像一个例外,但老实说,如果你有一些你想确保在 24 天和几天内以毫秒精度发生的事情那么在面对服务器和机器错误时,你应该使用比 setTimeout 更强大的东西......
2021-04-10 23:19:22

在此处查看计时器上的节点文档:https : //nodejs.org/api/timers.html(假设在 js 中也相同,因为它现在在基于事件循环中是如此普遍的术语

简而言之:

当延迟大于 2147483647 或小于 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();