大きなミリ秒の遅延値でsetTimeout()が「中断」するのはなぜですか?


104

大きなミリ秒の値をに渡すと、予期しない動作が発生しましたsetTimeout()。例えば、

setTimeout(some_callback, Number.MAX_VALUE);

そして

setTimeout(some_callback, Infinity);

どちらも、遅延として大きな数の代わりにsome_callbackパスしたかのように、ほぼ即座に実行さ0れます。

なぜこれが起こるのですか?

回答:


143

これは、32ビットintを使用して遅延を格納するsetTimeoutによるもので、最大許容値は

2147483647

あなたがしようとすると

2147483648

問題が発生します。

私はこれがJS Engineで何らかの内部例外を引き起こし、関数がまったく起動せずにすぐに起動することを推測しているだけです。


1
わかりました。実際には内部例外は発生しないと思います。代わりに、(1)整数オーバーフローを引き起こすか、または(2)内部で遅延を符号なし32ビットint値に強制します。(1)の場合は、実際には遅延に負の値を渡しています。(2)の場合、次のようなdelay >>> 0ことが発生するため、渡される遅延はゼロです。どちらにしても、遅延が32ビットの符号なし整数として格納されるという事実は、この動作を説明しています。ありがとう!
マットボール

古い更新ですが、上限が4999986177638349999861776384コールバックがすぐに発生する原因)であることがわかりました
maxp

7
@maxp理由は次のとおりです49999861776383 % 2147483648 === 2147483647
David Da SilvaContínApr

@DavidDaSilvaContín本当にこれに遅れますが、さらに説明できますか?なぜ2147483647が制限ではないのか理解できませんか?
Nick Coad

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を使用できなくなりました。
アランNienhuis

2
簿記を行い、この関数内でキャンセルするtimeoutIdを置き換える場合は、キャンセルする機能を実際に失うことはありません。
チャーラグ

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
私はサーバーのNodeJSでJavascriptを実行していますが、24.8日はまだ良いですが、コールバックが1か月(30日)で発生するように設定するより論理的な方法を探しています。これに行く方法は何でしょうか?
Paul

1
確かに、ブラウザウィンドウを24.8日より長く開いていました。ブラウザが内部でRonenのソリューションのようなことをしない、少なくともMAX_SAFE_INTEGERまでは奇妙です
acjay

1
誰が言う?私はブラウザを24日以上開いたままにしています...;)
Pete Alvin

2

タイマーのノードドキュメントをここで確認してください:https : //nodejs.org/api/timers.html(これはjsでも同じであると仮定します。これは、現在、イベントループベースでこのようなユビキタスな用語であるためです。

要するに:

遅延が2147483647より大きいか1より小さい場合、遅延は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.7976931348623157e + 308の代わりに1を返します。


13
これは誤りです:Number.MAX_VALUEは整数です。これは整数17976931348623157で、その後に292個のゼロが続きます。その理由のparseIntリターンは1、それが最初の文字列に引数を変換し、左から右に文字列を検索するためです。.(数値ではない)を見つけるとすぐに停止します。
Pauan、2014

1
ちなみに、何かが整数かどうかをテストする場合は、ES6関数を使用しますNumber.isInteger(foo)。ただし、まだサポートされていないため、Math.round(foo) === foo代わりに使用できます。
Pauan

2
@Pauanは、実装に関してNumber.MAX_VALUEは、整数ではなくdoubleです。つまり、JavaScriptで32ビットの整数を保存するために使用されるため、doubleは整数を表すことができます。
Alexis Wilke、2015

1
@AlexisWilkeはい、もちろんJavaScriptはすべての数値を64ビット浮動小数点として実装ます。「整数」が「32ビットバイナリ」を意味する場合Number.MAX_VALUE、整数ではありません。しかし、「整数」によって「整数」の精神的概念を意味する場合、それは整数です。JavaScriptでは、すべての数値が64ビット浮動小数点であるため、「整数」の精神的な概念の定義を使用するのが一般的です。
Pauan

そこにもありNumber.MAX_SAFE_INTEGERますが、ここで探している数でもありません。
震え
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.