136

I came across some unexpected behavior when passing a large millisecond value to setTimeout(). For instance,

setTimeout(some_callback, Number.MAX_VALUE);

and

setTimeout(some_callback, Infinity);

both cause some_callback to be run almost immediately, as if I'd passed 0 instead of a large number as the delay.

Why does this happen?

Matt Ball
  • 354,903
  • 100
  • 647
  • 710
  • 1
    Because it's limited to 32-bit, which is 2 power 32. If you calculate it you get 4294967296. Now, you need the first bit to decide whether it is a negative or positive number. So you get 2 power 31 and you get half of 4294967296, which is 2147483648. But zero is a positive number so 2147483647. – Joe Jan 21 '21 at 11:10

7 Answers7

179

This is due to setTimeout using a 32 bit int to store the delay so the max value allowed would be

2147483647

if you try

2147483648

you get your problem occurring.

I can only presume this is causing some form of internal exception in the JS Engine and causing the function to fire immediately rather than not at all.

Matt Ball
  • 354,903
  • 100
  • 647
  • 710
OneSHOT
  • 6,903
  • 2
  • 23
  • 24
  • 1
    Okay, that makes sense. I'm guessing it doesn't actually raise an internal exception. Instead, I see it either (1) causing an integer overflow, or (2) internally coercing the delay to an unsigned 32-bit int value. If (1) is the case, then I'm really passing a negative value for the delay. If it's (2), then something like [`delay >>> 0`](http://stackoverflow.com/questions/3081987/) happens, so the delay passed is zero. Either way, the fact that the delay is stored as a 32-bit unsigned int explains this behavior. Thanks! – Matt Ball Aug 12 '10 at 15:22
  • Old update, but i've just found the max limit is `49999861776383` (`49999861776384` causes the callback to fire instantly) – maxp Jan 09 '14 at 15:03
  • 11
    @maxp That's because `49999861776383 % 2147483648 === 2147483647` – David Da Silva Contín Apr 07 '15 at 16:08
  • @DavidDaSilvaContín really late to this, but can you explain further? Can't understand why 2147483647 isn't the limit? – Nick Coad May 25 '18 at 02:08
  • 4
    @NickCoad both numbers would delay the same amount (i.e. 49999861776383 is same as 2147483647 from a signed 32 bit point of view). write them out in binary, and take the last 31 bits, they will all be 1s. – Mark Fisher Jun 06 '18 at 07:47
  • Easy to remember because "the number 2,147,483,647 is the eighth Mersenne prime, equal to 2^31 − 1. It is one of only four known double Mersenne primes." [wikipedia] – Craig Hicks Dec 03 '20 at 07:52
  • `setTimeout` has note about it https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#maximum_delay_value – Stanislav Berkov Feb 03 '22 at 11:19
33

You can use:

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);
}
Ronen
  • 1,225
  • 13
  • 10
  • 2
    this is cool, but we loose the ability to useClearTimeout due to the recursion. – Allan Nienhuis Jan 12 '18 at 22:08
  • 3
    You don't really lose ability to cancel it provided that you do your bookkeeping and replace timeoutId you want to cancel inside this function. – charlag Jul 02 '19 at 13:42
28

Some explanation here: http://closure-library.googlecode.com/svn/docs/closure_goog_timer_timer.js.source.html

Timeout values too big to fit into a signed 32-bit integer may cause overflow in FF, Safari, and Chrome, resulting in the timeout being scheduled immediately. It makes more sense simply not to schedule these timeouts, since 24.8 days is beyond a reasonable expectation for the browser to stay open.

warpech
  • 6,293
  • 4
  • 35
  • 36
  • 2
    warpech's answer makes a lot of sense - a long running process like a Node.JS server might sound like an exception, but to be honest if you've got something that you want to ensure happens in exactly 24 and a bit days with millisecond accuracy then you should use something more robust in the face of server and machine errors than setTimeout... – cfogelberg Feb 09 '14 at 12:07
  • @cfogelberg, I have not seen the FF or any other implementation of the `setTimeout()`, but I would hope that they calculate the date and time when it should wake up and do not decrement a counter on some randomly defined tick... (One can hope, at least) – Alexis Wilke Dec 16 '15 at 08:28
  • 2
    I'm running Javascript in NodeJS on a server, 24.8 days is still good, but I'm looking for a more logical way to set an callback to happen in say 1 month (30 days). What would be the way to go for this? – Paul Jan 12 '17 at 11:39
  • 1
    I have, for sure, had browser windows open longer than 24.8 days. It's bizarre to me that browsers don't internally do something like Ronen's solution, at least up to MAX_SAFE_INTEGER – acjay Nov 03 '17 at 16:24
  • 2
    Who says? I keep my browser open way longer than 24 days... ;) – Pete Alvin Jul 04 '18 at 09:25
  • If you are using NodeJS on a server, like @Paul, give a look at [node-cron](https://github.com/node-cron/node-cron) – Washington Guedes Dec 10 '20 at 14:26
8

Check out the node doc on Timers here: https://nodejs.org/api/timers.html (assuming same across js as well since it's such an ubiquitous term now in event loop based

In short:

When delay is larger than 2147483647 or less than 1, the delay will be set to 1.

and delay is:

The number of milliseconds to wait before calling the callback.

Seems like your timeout value is being defaulted to an unexpected value along these rules, possibly?

SillyGilly
  • 81
  • 1
  • 2
1

I stumbled on this when I tried to automatically logout a user with an expired session. My solution was to just reset the timeout after one day, and keep the functionality to use clearTimeout.

Here is a little prototype example:

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);
    }
};

Usage:

var timer = new Timer('2018-08-17 14:05:00', function() {
    document.location.reload();
});

And you may clear it with the stopTimer method:

timer.stopTimer();
Tim
  • 2,805
  • 25
  • 21
1

Can't comment but to answer all the people. It takes unsigned value ( you can't wait negative milliseconds obviously ) So since max value is "2147483647" when you enter a higher value it start going from 0.

Basically delay = {VALUE} % 2147483647.

So using delay of 2147483648 would make it 1 millisecond, therefore, instant proc.

KYGAS
  • 27
  • 1
  • 2
    This is wrong. A value of `2787431818` will also run it instantly even though `2787431818 % 2147483647` is 639948171ms – choz May 27 '21 at 22:55
-3
Number.MAX_VALUE

is actually not an integer. The maximum allowable value for setTimeout is likely 2^31 or 2^32. Try

parseInt(Number.MAX_VALUE) 

and you get 1 back instead of 1.7976931348623157e+308.

Osmund
  • 772
  • 6
  • 13
  • 13
    This is incorrect: `Number.MAX_VALUE` is an integer. It is the integer 17976931348623157 with 292 zeros after. The reason `parseInt` returns `1` is because it first converts its argument to a string and then searches the string from left to right. As soon as it finds the `.` (which isn't a number), it stops. – Pauan Jul 28 '14 at 11:01
  • 1
    By the way, if you want to test if something is an integer, use the ES6 function `Number.isInteger(foo)`. But since it's not supported yet, you can use `Math.round(foo) === foo` instead. – Pauan Feb 06 '15 at 00:02
  • 2
    @Pauan, implementation wise, `Number.MAX_VALUE` is not an integer but a `double`. So there is that... A double can represent an integer, though, since it is used to save integers of 32 bits in JavaScript. – Alexis Wilke Dec 16 '15 at 08:31
  • 1
    @AlexisWilke Yes, of course JavaScript implements **all** numbers as 64-bit floating point. If by "integer" you mean "32-bit binary" then `Number.MAX_VALUE` is not an integer. But if by "integer" you mean the mental concept of "an integer", then it is an integer. In JavaScript, because all numbers are 64-bit floating point, it is common to use the mental concept definition of "integer." – Pauan Dec 16 '15 at 09:42
  • There's also `Number.MAX_SAFE_INTEGER` but that's not the number we're looking for here either. – tremby Sep 13 '19 at 22:22