GoldenNewby
GoldenNewby

Reputation: 4452

Execute settimeout early

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?

Upvotes: 8

Views: 14473

Answers (5)

Pebbl
Pebbl

Reputation: 35995

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

Upvotes: 4

Jan Wikholm
Jan Wikholm

Reputation: 1575

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.

Upvotes: 6

wong2
wong2

Reputation: 35710

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

Upvotes: 1

Cᴏʀʏ
Cᴏʀʏ

Reputation: 107508

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

Upvotes: 9

pete
pete

Reputation: 25081

Use clearTimeout and reschedule for an earlier time.

Upvotes: 0

Related Questions