Execute settimeout early

11,775

Solution 1

So, if you make whatever you're delaying its own function:

function sayHi() {
    alert('hi');
}

You can use a timeout and a regular function call:

var timeout = setTimeout(sayHi, 10000); // say hi after 10 seconds

Or to call it before the timeout expires, just call the function whenever you need to:

sayHi();

Am I on the right track here? If you need to cancel the timeout, call clearTimeout() on your timeout variable.

if (timeout)
    clearTimeout(timeout);

Solution 2

You cannot track it with the standard setTimeout, but Javascript allows you to enhance features as you wish.

For example you could have your own enhanced setTimeout:

var _setTimeout = window.setTimeout;
var timeouts = [];
window.setTimeout = function(fn, ms) {
    var id = _setTimeout(fn, ms);
    timeouts[id] = fn;
    return id;
};

window.premature = function(id) {
    var fn = timeouts[id];
    if (fn) {
        clearTimeout(id);
        if (fn instanceof String) {
            eval(fn);
        } else {
            fn()
        }
    }
};


function printDate(str) {
    $("body").append("<p>" + str + ". " + new Date() + "</p>");

}

$(function() {
    var id1 = setTimeout(function() { printDate("id1"); }, 10000);
    var id2 = setTimeout(function() { printDate("id2"); }, 10000);
    printDate("first one");
    // just to demonstrate that the string version works too
    setTimeout("window.premature(" + id1 +")", 5000);
});

You can see it in action at jsFiddle

Do note, that this simple hack does not take into account clearing used ids when the timeouts do occur, but it is just to show that you can do this sort of thing in Javascript if you really need it.

Solution 3

polyfill solution

Here is some javascript that I've updated from a previous project, it is extended now with trigger and update methods; it is similar to Jan Wikholm's solution (+1) — but a bit more complete, taking into account clears, passing of arguments, and the prevention of eval if required:

(function(keep){

  /// a few things to remember
  keep.setTimeout = window.setTimeout;
  keep.clearTimeout = window.clearTimeout;
  keep.TO = function(){};
  keep.list = {};
  keep.settings = {
    eval: false /// set this to true if you wish to support string timeouts
  };

  /**
   * Quick check function to prevent eval
   */
  keep.checkParam = function( param ){
    if ( !keep.settings.eval && typeof param == 'string' ) {
      throw new Error('setTimeout blocked evaluation of string, ' + 
        'use a function instead.');
      return false;
    }
    else if ( param ) {
      return true;
    }
  };

  /**
   * Simple function constructor to avoid trapping unwanted references
   */
  keep.makeFunction = function(data){
    return function(args){
      /// copy our args array
      args = data.args.slice();
      /// do we allow eval?
      if ( keep.settings.eval ) {
        /// if so, reuse setTimeout for it's abilities
        args[0] = data.param; /// use the original param
        args[1] = 0;          /// trigger immediately
        keep.setTimeout.apply( window, args );
      }
      // more secure, assume dealing with function -- not string
      else if ( keep.checkParam( data.param ) && data.param.apply ) {
        data.param.apply( window, args.slice(2) );
      }
      else {
        throw new Error('unsupported param for setTimeout' + 
          ' i.e. non-function used.');
      }
      /// clear our storage of this tid
      window.clearTimeout( data.tid );
    };
  };

  /**
   * Sets timeouts just like you would expect
   */
  window.setTimeout = function( param, timeout ){
    if ( keep.checkParam( param ) ) {
      var tid, data;
      /// support passing a timeout object as param
      if ( param instanceof keep.TO ) {
        data = param;
        data.args[1] = data.timeout;
      }
      else {
        /// create an object to store the timeout info
        data = new keep.TO();
        data.func = keep.makeFunction(data);
        data.param = param;
        data.timeout = timeout;
        data.args = Array.prototype.slice.call(arguments,0);
        data.args[0] = data.func;
      }
      data.tid = keep.setTimeout.apply( window, data.args );
      keep.list[data.tid] = data;
      /// enhance the returned number to support .clear, .trigger and .update
      tid = new Number(data.tid);
      tid.clear = window.clearTimeout;
      tid.trigger = window.triggerTimeout;
      tid.update = window.updateTimeout;
      return tid;
    }
  };

  /**
   * Clearing timeouts since 2013
   */
  window.clearTimeout = function( tid ){
    if ( this instanceof Number ) {
      tid = 0 + this;
    }
    var obj;
    if ( (obj = window.getTimeout(tid)) ) {
      delete keep.list[tid];
      keep.clearTimeout.call(window, tid);
    }
  };

  /**
   * Returns the internal timeout storage object
   */
  window.getTimeout = function( tid ){
    var obj;
    if ( (obj = keep.list[tid]) ) {
      return obj;
    }
  };

  /**
   * Clears and fires a timeout before it's outed time
   */    
  window.triggerTimeout = function( tid ){
    if ( this instanceof Number ) {
      tid = 0 + this;
    }
    var obj;
    if ( (obj = window.getTimeout(tid)) ) {
      window.clearTimeout(tid);
      obj.func.call(window);
    }
    else {
      throw new Error('No Timeout found to trigger for ID '+ tid);
    }
  };

  /**
   * Clears and recreates an existing timeout, returns a new timeout id.
   */
  window.updateTimeout = function( tid, timeout ){
    if ( this instanceof Number ) {
      if ( arguments.length == 1 ) {
        timeout = tid;
      }
      tid = 0 + this;
    }
    var obj;
    if ( (obj = window.getTimeout(tid)) ) {
      obj.timeout = timeout;
      window.clearTimeout(tid);
      return window.setTimeout(obj);
    }
    else {
      throw new Error('No Timeout found to update for ID ' + tid);
    }
  };

  /**
   * Utility function to tidy up
   */
  window.clearAllTimeouts = function(){
    for ( var i in keep.list ) {
      window.clearTimeout(i);
    };
  };

  /// Tidy up
  window.onunload = (function(previous){
    return function(){
      window.clearAllTimeouts();
      keep.list = {};
      previous && previous.call(window);
    };
  }(window.onunload));

})({});

include

Just put the above in a js file and include into your page using a normal script tag, the code does not need to be invoked in any way:

<script src="timeouts.js"></script>

usage

Obviously this should be used just like a normal setTimeout call, however you now have extra methods that should give more flexibility.

var tid = setTimeout( function(){ alert('OK Computer') }, 2000 );

For example you could cancel the original and force the timeout to trigger earlier:

setTimeout( function(){ triggerTimeout( tid ); }, 500 );

Or, you could update the timeout (make sure we remember the new returned tid):

setTimeout( function(){ tid = updateTimeout( tid, 5000 ); }, 500 );

You can also do the usual:

setTimeout( function(){ clearTimeout( tid ); }, 1000 );

Each of these methods are also accessible via the tid itself:

setTimeout( function(){ tid.trigger(); }, 1000 );
setTimeout( function(){ tid.update( 5000 ); }, 1000 );
setTimeout( function(){ tid.clear(); }, 1000 );

By default this code prevents the use of setTimeout with a string param, mainly because it is a far better coding style to pass functions rather than strings. To change this you can switch the following setting to true:

keep.settings = {
  eval: true
};

This is not recommended however.

There is also an added benefit to leaving eval disabled, in the fact that the code will use normal function calling to trigger the timeout i.e. .apply(). This means that whatever browser you are using, you can pass arguments to the timeout function via setTimeout — which is not normally something you can rely on cross-browser. e.g:

setTimeout( function(a){ alert(a) }, 2000, 'Hello World' );

Solution 4

Just put the function out and give it a name:

function handler(){ 
    alert('hi'); 
}

timeout = setTimeout(handler, 10000);  

then you can call it in other places with handler();

Share:
11,775
GoldenNewby
Author by

GoldenNewby

Updated on July 06, 2022

Comments

  • GoldenNewby
    GoldenNewby almost 2 years

    I am using debouncing to execute events after a timeout using settimeout. The problem I have, is that other javascript events expect those events to occur synchronously. Since they are now executing after a timeout, I'd like to be able to trigger them prematurely by other javascript events (so those events requiring them won't fail).

    Anywhom, if I do something like:

    timeout = setTimeout(function() { alert('hi'); }, 10000);
    

    , and I want that to occur before 10 seconds passes, how can I do that?

    The solution can involve jquery if necessary. Thanks!

    Edit: Is it possible to do this with just access to the timeout object?

  • GoldenNewby
    GoldenNewby about 12 years
    Sorry for not being clear. I was curious if it was possible with just the return value of setTimeout.
  • Cᴏʀʏ
    Cᴏʀʏ about 12 years
    @GoldenNewby: The return value from setTimeout() is just an ID so you can track it (and cancel it). Not much purpose for it otherwise.
  • Pebbl
    Pebbl over 10 years
    @Cory +1 But I'd have to disagree with Not much purpose for it otherwise. If you have an registered ID it would make sense to me that you could have a triggerTimeout, getTimeout or even updateTimeout methods along with set and clear -- sadly they don't exist though, but they could be written as a polyfill include with bespoke overrides for setTimeout and clearTimeout.
  • niaccurshi
    niaccurshi over 9 years
    Brilliant, this is exactly what I was looking for information about, great that I don't need to go through writing it myself. Thanks :D
  • doug65536
    doug65536 over 7 years
    The real setTimeout takes more than two parameters, any parameters after the millisecond parameter are passed to the callback. Example: setTimeout(alert, 2000, 'this is passed to alert two seconds later');