밀리 초 지연 값이 큰 경우 setTimeout ()이 "중단"되는 이유는 무엇입니까?


104

큰 밀리 초 값을에 전달할 때 예기치 않은 동작이 발생했습니다 setTimeout(). 예를 들어

setTimeout(some_callback, Number.MAX_VALUE);

setTimeout(some_callback, Infinity);

두 가지 모두 지연으로 큰 숫자 대신 some_callback통과 한 것처럼 거의 즉시 실행 0됩니다.

왜 이런 일이 발생합니까?

답변:


143

이는 지연을 저장하기 위해 32 비트 정수를 사용하는 setTimeout 때문이므로 허용되는 최대 값은

2147483647

시도하면

2147483648

문제가 발생합니다.

나는 이것이 JS 엔진에서 어떤 형태의 내부 예외를 일으키고 함수가 전혀 발생하지 않고 즉시 실행되도록 가정 할 수 있습니다.


1
좋아요, 말이 되네요. 실제로 내부 예외가 발생하지 않는다고 생각합니다. 대신, (1) 정수 오버플로를 유발하거나 (2) 내부적으로 지연을 부호없는 32 비트 정수 값으로 강제 변환하는 것을 봅니다. (1)이 해당되면 실제로 지연에 대해 음수 값을 전달합니다. (2)이면 다음과 같은 delay >>> 0일이 발생하므로 전달 된 지연은 0입니다. 어느 쪽이든 지연이 32 비트 unsigned int로 저장된다는 사실이이 동작을 설명합니다. 감사!
Matt Ball

이전 업데이트이지만 최대 한도가 49999861776383( 49999861776384콜백이 즉시 실행되도록 함)
maxp

7
@maxp 그 이유는49999861776383 % 2147483648 === 2147483647
David Da Silva Contín 2015-04-07

@ DavidDaSilvaContín이 정말 늦었지만 더 설명해 주시겠습니까? 2147483647이 제한이 아닌 이유를 이해할 수 없습니까?
Nick Coad

2
@NickCoad 두 숫자는 동일한 양을 지연시킵니다 (즉, 49999861776383은 부호있는 32 비트 관점에서 2147483647과 동일합니다). 바이너리로 작성하고 마지막 31 비트를 취하면 모두 1이됩니다.
마크 피셔

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을 사용할 수있는 기능이 없습니다.
Allan Nienhuis

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

@cfogelberg, FF 또는의 다른 구현을 보지 못했지만 깨어나야 setTimeout()할 날짜와 시간을 계산하고 무작위로 정의 된 틱에서 카운터를 줄이지 않기를 바랍니다. 적어도)
알렉시스 WILKE

2
저는 서버의 NodeJS에서 Javascript를 실행하고 있습니다. 24.8 일은 여전히 ​​좋지만 1 개월 (30 일) 내에 콜백이 발생하도록 설정하는보다 논리적 인 방법을 찾고 있습니다. 이것을 위해 갈 방법은 무엇입니까?
Paul

1
확실히 브라우저 창을 24.8 일 이상 열었습니다. 브라우저가 최소한 MAX_SAFE_INTEGER까지 내부적으로 Ronen의 솔루션과 같은 작업을 수행하지 않는다는 것이 저에게 기이합니다.
acjay

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 밀리 초가되므로 즉시 처리됩니다.


-2
Number.MAX_VALUE

실제로 정수가 아닙니다. setTimeout의 최대 허용 값은 2 ^ 31 또는 2 ^ 32입니다. 시험

parseInt(Number.MAX_VALUE) 

1.7976931348623157e + 308 대신 1 개를 돌려받습니다.


13
이것은 올바르지 않습니다 : Number.MAX_VALUE정수입니다. 뒤에 0이 292 개있는 정수 17976931348623157입니다. parseInt반환 되는 이유 는 1먼저 인수를 문자열로 변환 한 다음 문자열을 왼쪽에서 오른쪽으로 검색하기 때문입니다. .(숫자가 아닌)를 찾으면 바로 멈 춥니 다.
Pauan 2014-07-28

1
그건 그렇고, 무언가가 정수인지 테스트하려면 ES6 함수를 사용하십시오 Number.isInteger(foo). 그러나 아직 지원되지 않으므로 Math.round(foo) === foo대신 사용할 수 있습니다 .
Pauan 2015

2
구현 측면에서 @Pauan Number.MAX_VALUE은 정수가 아니라 double. 그래서 ... double은 정수를 나타낼 수 있습니다. JavaScript에서 32 비트의 정수를 저장하는 데 사용되기 때문입니다.
Alexis Wilke 2015

1
@AlexisWilke 예, 물론 JavaScript는 모든 숫자를 64 비트 부동 소수점으로 구현 합니다 . "정수"가 "32 비트 바이너리"를 의미 Number.MAX_VALUE하면 정수가 아닙니다. 그러나 "정수"가 "정수"의 정신적 개념을 의미한다면 그것은 정수입니다. JavaScript에서는 모든 숫자가 64 비트 부동 소수점이므로 "정수"라는 정신적 개념 정의를 사용하는 것이 일반적입니다.
Pauan

또한 Number.MAX_SAFE_INTEGER여기에서 찾고있는 숫자가 아닙니다.
tremby
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.