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

问题描述 投票:0回答:7

将大毫秒值传递给

setTimeout()
时,我遇到了一些意外的行为。例如,

setTimeout(some_callback, Number.MAX_VALUE);

setTimeout(some_callback, Infinity);

两者都会导致

some_callback
几乎立即运行,就好像我通过了
0
而不是一个很大的数字作为延迟。

为什么会出现这种情况?

javascript settimeout
7个回答
183
投票

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

2147483647

如果你尝试

2147483648

你的问题就出现了。

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


34
投票

您可以使用:

const MAX_INT32 = 2 ** 31 - 1 // 2147483647 (hex 0x7FFFFFFF)

function runAtDate(date, func) {
    const now = Date.now();
    const then = date.valueOf();
    const diff = Math.max(then - now, 0);
    if (diff > MAX_INT32) {
        setTimeout(() => {
            runAtDate(date, func, MAX_INT32);
        }, MAX_INT32);
    } else {
        setTimeout(func, diff);
    }
}

或者,稍微复杂一点的版本,允许您使用返回值清除超时:

const MAX_INT32 = 2 ** 31 - 1 // 2147483647 (hex 0x7FFFFFFF)

function runAtDate(date, func, msPerTimeout = MAX_INT32) {
    const timeout = arguments[3] ?? { valueOf() { return this._val; } };
    const now = Date.now();
    const then = date.valueOf();
    const diff = Math.max(then - now, 0);
    if (diff > msPerTimeout) {
        timeout._val = setTimeout(() => {
            runAtDate(date, func, msPerTimeout, timeout);
        }, msPerTimeout);
    } else {
        timeout._val = setTimeout(func, diff);
    }

    return timeout
}

// Usage (using a much lower `msPerTimeout` value for demo purposes)

void (async () => {
    const delay = (ms) => new Promise((res) => setTimeout(res, ms));

    // v1 (running to completion)
    {
        const timeout = runAtDate(
            Date.now() + 1000,
            () => console.log('v1 done!'),
            100,
        );
        console.log('v1 @ 0ms:', timeout.valueOf());
        await delay(500);
        console.log('v1 @ 500ms:', timeout.valueOf());
        await delay(500);
        console.log('v1 @ 1000ms:', timeout.valueOf());
    }

    console.log('---')
    
    // v2 (clearing the timeout)
    {
        const timeout = runAtDate(
            Date.now() + 1000,
            () => console.log('v2 done! (you’ll never see this message)'),
            100,
        );
        console.log('v2 @ 0ms:', timeout.valueOf());
        await delay(500);
        console.log('v2 @ 500ms:', timeout.valueOf());
        
        clearTimeout(timeout);
        
        await delay(500);
        console.log('v2 @ 1000ms:', timeout.valueOf());
    }
})();


29
投票

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

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


8
投票

在此处查看有关计时器的节点文档: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();

1
投票

无法发表评论,只能回答所有人。 它需要无符号值(显然你不能等待负毫秒) 因此,由于最大值为“2147483647”,当您输入更高的值时,它会从 0 开始。

基本上延迟 = {VALUE} % 2147483647。

因此使用 2147483648 的延迟将使其变为 1 毫秒,因此,即时过程。


-3
投票
Number.MAX_VALUE

实际上不是一个整数。 setTimeout 的最大允许值可能是 2^31 或 2^32。 尝试一下

parseInt(Number.MAX_VALUE) 

您将得到 1,而不是 1.7976931348623157e+308。

© www.soinside.com 2019 - 2024. All rights reserved.