8

The problem

If the delay is more than 2147483648 milliseconds(24.8551 days) the function will fire immediately.

Example

setTimeout(function(){ console.log('hey') }, 2147483648) // this fires early
setTimeout(function(){ console.log('hey') }, 2147483647) // this works properly

I tried it under Chrome v26 and Node.js v8.21

Adam Halasz
  • 57,421
  • 66
  • 149
  • 213
  • v8 must store the delay as a 32 bit signed integer. If you overflow, you'll get a negative number. Which logically, would execute right away. – Daniel May 01 '13 at 08:15
  • possible duplicate of [Why does setTimeout() "break" for large millisecond delay values?](http://stackoverflow.com/questions/3468607/why-does-settimeout-break-for-large-millisecond-delay-values) – Oriol Jan 28 '14 at 23:35

2 Answers2

11

The upper limit of setTimeout is 0x7FFFFFFF (or 2147483647 in decimal)

This is because setTimeout uses a 32bit integer to store its delay value, so anything above that will cause the problem

If you want a timeout which fires after an X ammount of days, you could try to use setInterval instead with a lower delay value like this

function setDaysTimeout(callback,days) {
    // 86400 seconds in a day
    var msInDay = 86400*1000; 

    var dayCount = 0;
    var timer = setInterval(function() {
        dayCount++;  // a day has passed

        if(dayCount == days) {
           clearInterval(timer);
           callback.apply(this,[]);
        }
    },msInDay);
}

You would then use it like this

setDaysTimeout(function() {
     console.log('Four days gone');
},4); // fire after 4 days
lostsource
  • 21,070
  • 8
  • 66
  • 88
  • Omg, that's very bad, than how can I make a timeout longer? – Adam Halasz May 01 '13 at 08:16
  • @Adam I updated the answer with a possible solution using `setInterval` with a smaller delay (instead of `setTimeout`) – lostsource May 01 '13 at 08:26
  • This isn't that great because there are reasons that the interval may not fire exactly when you want. What is much better is to calculate the time you want the event to happen, then set an interval (if >= 1 day) or a timeout (if < 1 day) for a shorter period that will check if it is past the correct time (or within some margin of error). Assuming intervals fire at exactly the right time day after day is a mistake. – ErikE Mar 07 '17 at 18:02
  • A day is 31,556,952,000ms/365. For longer periods I might consider using the length of an average day. – ThickMiddleManager Jan 22 '20 at 07:27
10

Since you are limited to 32 bits, just wrap setTimeout in a recursive function like so:

function setLongTimeout(callback, timeout_ms)
{

 //if we have to wait more than max time, need to recursively call this function again
 if(timeout_ms > 2147483647)
 {    //now wait until the max wait time passes then call this function again with
      //requested wait - max wait we just did, make sure and pass callback
      setTimeout(function(){ setLongTimeout(callback, (timeout_ms - 2147483647)); },
          2147483647);
 }
 else  //if we are asking to wait less than max, finally just do regular setTimeout and call callback
 {     setTimeout(callback, timeout_ms);     }
}

This isn't too complicated and should be extensible up to the limit of javascript number which is 1.7976931348623157E+10308, which by that number of milliseconds, we will all be dead and gone.

Too make it so you can have the ability to setLongTimeout, you could modify the function to accept an object which is passed by reference and thus retain scope back to the calling function:

function setLongTimeout(callback, timeout_ms, timeoutHandleObject)
{
 //if we have to wait more than max time, need to recursively call this function again
 if(timeout_ms > 2147483647)
 {    //now wait until the max wait time passes then call this function again with
      //requested wait - max wait we just did, make sure and pass callback
      timeoutHandleObject.timeoutHandle = setTimeout(function(){ setLongTimeout(callback, (timeout_ms - 2147483647), timeoutHandleObject); },
          2147483647);
 }
 else  //if we are asking to wait less than max, finally just do regular setTimeout and call callback
 {     timeoutHandleObject.timeoutHandle = setTimeout(callback, timeout_ms);     }
}

Now you can call the timeout and then cancel it later if you needed like so:

var timeoutHandleObject = {};
setLongTimeout(function(){ console.log("Made it!");}, 2147483649, timeoutHandleObject);
setTimeout(function(){ clearTimeout(timeoutHandleObject.timeoutHandle); }, 5000);
Brian
  • 3,264
  • 4
  • 30
  • 43
  • The problem with this function is that it is very awkward to implement a clearLongTimeout function. – Freeman Lambda May 18 '16 at 15:09
  • 1
    fair enough, check out my edit to add the ability to cancel the timeout, not too much added complexity to get it done – Brian May 18 '16 at 20:43
  • Your new function has a race condition. It won't work correctly in the case that this series of events happens: 1. The client reads the `timeoutHandle` value. 2. The timeout occurs and the `setLongTimeout` function resets the timeout. 3. The client uses the `timeoutHandle` handle to clear the (already-fired) timeout. – ErikE Mar 07 '17 at 18:05
  • 1
    To fix this, instead of accepting a `timeoutHandleObject` return a function which can be used to clear the timeout. Then the returned function has access to your inner variables and can work with the correct state of the object. – ErikE Mar 07 '17 at 18:06
  • Isn't that only a race condition if the calling function stores timeoutHandleObject.timeoutHandle in another variable and lets timeoutHandleObject go out of scope? The way I've got it demonstrated in the example, you use the object and reference the member variable explicitly when you want to clear the timeout. I'm pretty sure as long as you retain the timeoutHandleObject in your scope, you have no race condition. But I do understand what you are saying that if you grab timeoutHandleObject.timeoutHandle you can lose the ability to cancel the timeout later on. – Brian Mar 08 '17 at 14:53
  • @ErikE there can't be a race condition in a single-threaded language. But I liked the idea of returning "clearTimeout" function instead of passing an object. – pumbo Oct 29 '18 at 11:54