24

What is the most recommended/best way to stop multiple instances of a setTimeout function from being created (in javascript)?

An example (psuedo code):

function mouseClick()
{
   moveDiv("div_0001", mouseX, mouseY);
}

function moveDiv(objID, destX, destY)
{
   //some code that moves the div closer to destination
   ...
   ...
   ...

   setTimeout("moveDiv(objID, destX, destY)", 1000);
   ...
   ...
   ...
}

My issue is that if the user clicks the mouse multiple times, I have multiple instances of moveDiv() getting called.

The option I have seen is to create a flag, that only allows the timeout to be called if no other instance is available...is that the best way to go?

I hope that makes it clear....

Markus
  • 1,539
  • 1
  • 18
  • 22

9 Answers9

30

when you call settimeout, it returns you a variable "handle" (a number, I think)

if you call settimeout a second time, you should first

clearTimeout( handle )

then:

handle = setTimeout( ... )

to help automate this, you might use a wrapper that associates timeout calls with a string (i.e. the div's id, or anything you want), so that if there's a previous settimeout with the same "string", it clears it for you automatically before setting it again,

You would use an array (i.e. dictionary/hashmap) to associate strings with handles.

var timeout_handles = []    
function set_time_out( id, code, time ) /// wrapper
{
    if( id in timeout_handles )
    {
        clearTimeout( timeout_handles[id] )
    }

    timeout_handles[id] = setTimeout( code, time )
}

There are of course other ways to do this ..

Brandon Rhodes
  • 83,755
  • 16
  • 106
  • 147
hasen
  • 161,647
  • 65
  • 194
  • 231
  • Uhm, actually, your code does not really suppress multiple timeout "threads". (Note how the timeout in the question is recursive.) More is needed. ;) – Már Örlygsson Nov 24 '08 at 19:28
4

I would do it this way:

// declare an array for all the timeOuts
var timeOuts = new Array();  

// then instead of a normal timeOut call do this
timeOuts["uniqueId"] = setTimeout('whateverYouDo("fooValue")', 1000);  

// to clear them all, just call this
function clearTimeouts() {  
  for (key in timeOuts) {  
    clearTimeout(timeOuts[key]);  
  }  
}  

// clear just one of the timeOuts this way
clearTimeout(timeOuts["uniqueId"]); 
splattne
  • 102,760
  • 52
  • 202
  • 249
  • Do I understand it correctly? Everytime you call `setTimeout` you are overwriting value in your (one and only) array item `timeOuts["uniqueId"]` so it means traversing that array in the function `clearTimeouts()` will not do what you expect... – Enriqe Aug 31 '14 at 21:51
  • 'setTimeout()' should not be called with a string as the first parameter - it should be a function reference or an anonymous function, but not a string. A string will affect the scope that the function runs in. – Scott Marcus May 24 '16 at 23:25
2
var timeout1 = window.setTimeout('doSomething();', 1000);
var timeout2 = window.setTimeout('doSomething();', 1000);
var timeout3 = window.setTimeout('doSomething();', 1000);

// to cancel:
window.clearTimeout(timeout1);
window.clearTimeout(timeout2);
window.clearTimeout(timeout3);
Daniel Schaffer
  • 56,753
  • 31
  • 116
  • 165
2

I haven't tested any of this, and just cut this up in the editor here. Might work, might not, hopefully will be food for thought though.

var Timeout = { 
   _timeouts: {}, 
   set: function(name, func, time){ 
     this.clear(name); 
     this._timeouts[name] = {pending: true, func: func}; 
     var tobj = this._timeouts[name];
     tobj.timeout = setTimeout(function()
     { 
/* setTimeout normally passes an accuracy report on some browsers, this just forwards that. */
       tobj.func.call(arguments); 
       tobj.pending = false;
     }, time); 
   },
   hasRun: function(name)
   { 
       if( this._timeouts[name] ) 
       {
          return !this._timeouts[name].pending; 
       }
       return -1; /* Whut? */ 
   },
   runNow: function(name)
   {
      if( this._timeouts[name] && this.hasRun(name)===false )
      {
         this._timeouts[name].func(-1); /* fake time. *shrug* */
         this.clear(name);
      }
   } 
   clear: function(name)
   {
     if( this._timeouts[name] && this._timeouts[name].pending ) 
     {
       clearTimeout(this._timeouts[name].timeout); 
       this._timeouts[name].pending = false; 
     }
   }
};

Timeout.set("doom1", function(){ 
  if(  Timeout.hasRun("doom2") === true )
  {
     alert("OMG, it has teh run");  
  }
}, 2000 ); 
Timeout.set("doom2", function(){ 
   /* NooP! */
}, 1000 ); 

Successive calls with the same identifier will cancel the previous call.

Kent Fredric
  • 56,416
  • 14
  • 107
  • 150
1

You could store multiple flags in a lookup-table (hash) using objID as a key.

var moving = {};

function mouseClick()
{
  var objID = "div_0001";
  if (!moving[objID])
  {
    moving[objID] = true;
    moveDiv("div_0001", mouseX, mouseY);
  }
}
Már Örlygsson
  • 14,176
  • 3
  • 42
  • 53
1

You can avoid a global or lesser variable by using a property within the function. This works well if the function is only used for this specific context.

function set_time_out( id, code, time ) /// wrapper
{
  if(typeof this.timeout_handles == 'undefined') this.timeout_handles = [];

        if( id in this.timeout_handles )
        {
                clearTimeout( this.timeout_handles[id] )
        }

        this.timeout_handles[id] = setTimeout( code, time )
}
RudiBR
  • 117
  • 10
0

I'm using this to force a garbage collection on all obsolete timeout references which really un-lagged my script preformance:

var TopObjList = new Array();
function ColorCycle( theId, theIndex, RefPoint ) {
    ...
    ...
    ...
    TopObjList.push(setTimeout( function() { ColorCycle( theId, theIndex ,CCr ); },CC_speed));
    TO_l = TopObjList.length;
    if (TO_l > 8888) {
        for (CCl=4777; CCl<TO_l; CCl++) {
            clearTimeout(TopObjList.shift());
            }
        }
    }

My original sloppy code was generating a massive array 100,000+ deep over a very short time but this really did the trick!

Bruce
  • 1
  • 1
0

you can always overwrite the buttons onclick to return false. example:

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="UTF-8">
<head>
    <title>Javascript example</title>
    <script type="text/javascript">         
        var count = 0;
        function annoy() {
            document.getElementById('testa').onclick = function() { return false; };

            setTimeout(function() {
                alert('isn\'t this annoying? ' + count++);
                document.getElementById('testa').onclick = window.annoy;
            }, 1000);

        }
    </script>
</head>
<body>
    <h2>Javascript example</h2>
    <a href="#" onClick="annoy()" id="testa">Should Only Fire Once</a><br />
</body>
</html>
Jared
  • 8,390
  • 5
  • 38
  • 43
0

You can set a global flag somewhere (like var mouseMoveActive = false;) that tells you whether you are already in a call and if so not start the next one. You set the flag just before you enter the setTimeout call, after checking whether it's already set. Then at the end of the routine called in setTimeout() you can reset the flag.

Rick Strahl
  • 17,302
  • 14
  • 89
  • 134