Home > front-end development, javascript > Best practice: Poll instead of a setTimeout hack

Best practice: Poll instead of a setTimeout hack

Very often you'll have events happening asynchronously, but you need to wait until one has completed before you fire the second.
And you may not have the ability of attaching a callback function to the first.

In my less wise days I'd say "Lets just setTimeout it for a couple seconds…" but always felt really dirty about it.

A classier approach I've used lately is to poll for a change. Here I'm using the gmail greasemonkey API and waiting for it to load in before I start using it:

gmonkey.load("1.0");
 
// this is a self-executing anonymous function that uses setTimeout to call itself 
// at 50ms intervals until the isLoaded variable resolves as true.
 
(function(){
  if (gmonkey.isLoaded){
    // do stuff i want to do with the API
    gmonkey.get('1.0').addNavModule('notepad', '<iframe src="http://aaronboodman.com/halfnote/"></iframe>');
  } else {
    setTimeout(arguments.callee,50);
  }
})();

I found myself doing this a lot when loading in multiple external resources and playing with them.. So to generalize the code I wrote executeWhenLoaded():

// executeWhenLoaded() will be overloaded with as many arguments as i want to check for presence.
function executeWhenLoaded(func){  
 
  for (var i = 1; i<arguments.length; i++){ // for loop starts at 1 to skip the function argument.
    if (! window[ arguments[i] ]) {
      setTimeout(arguments.callee,50);      
      return;
    } 
  }
 
  func(); // only reaches here when for loop is satisfied.
}
 
// and in use:
 
executeWhenLoaded(function(){ 
    console.log(session.data);
},'session');   // session will return a value when the whatever preceding functionality is done.

executeWhenLoaded's first argument is the function to call, it can an unlimited number of arguments, which are all strings that reflect objects in the global namespace that have to be present in order to execute that function.

Update: In the comments, ProggerPete notes that this is not cross-browser compatible.. yet! In IE6, at least, the browser loses the original reference to the arguments object when it cycles through on the arguments.callee call. He offers a fix below.

front-end development, javascript

  1. May 13th, 2008 at 15:41 #1

    mootools has a similar thing but it's only for AJAX calls (I think):

    http://demos.mootools.net/Group

  2. May 14th, 2008 at 10:47 #2

    The idea is good, but it doesn't seem to work (at least under IE6) as it loses the arguments when called by the timeout.

    This seems to do the trick though…

    function executeWhenLoaded(func){  
    	var intRef, argRef = arguments;
     
    	function executeCheck()	{
    		// for loop starts at 1 to skip the function argument.
    		for (var i = 1; i<argRef.length; i++){
    			if (! window[ argRef[i] ]){
    				if (!intRef) intRef = setInterval(executeCheck,50);      
    				return;
    			}
    		} 
    		clearInterval(intRef);
    		func(); // only reaches here when for loop is satisfied.
    	};
    	executeCheck();
    }
     
    function setFoo(){ window.foo = "Tricksy"; }
    function showFoo(){ alert(window.foo); }
     
    setTimeout(setFoo, 1000);
     
    executeWhenLoaded(showFoo, "foo");
  3. May 14th, 2008 at 11:16 #3

    Excellent suggestion, Pete.

    I must admit I was using this technique in a greasemonkey script and therefore in FF only.

    Will update the post.

  4. May 14th, 2008 at 13:17 #4

    I sped things up a bit below. Basically, if we know an argument exists, we can shift( ) it right off the arguments array, saving a bunch of iterations on that for (or in this case, while) loop.

                function executeWhenLoaded(f,args){
                    if(!args){
                        arguments.shift();
                        var args = arguments;  
                    } 
                    while(args.length &gt; 0){
                        if(window[args[0]]){ args.shift(); }
                        else{
                            setTimeout(function(){
                                executeWhenLoaded(f,args);
                            },50)
                        }
                    }
                    f();
                }

    Caveat: untested. :)

  5. May 15th, 2008 at 13:05 #5

    If you need to load JavaScript files (or CSS, whatever) MooTools provides the Asset helper. It adds a cross-browser onload event, no polling or callback from the JS file needed.

    Polling example from MooTools DomReady:

    var temp = document.createElement('div');
    (function(){
    	($try(function(){
    		temp.doScroll('left');
    		return $(temp).inject(document.body).set('html', 'temp').dispose();
    	})) ? domready() : arguments.callee.delay(50);
    })();
  6. May 22nd, 2008 at 10:45 #6

    btw- You can consider my code MIT licensed.
    Nah, fuck it. It's public domain. Go wild.

  7. Tom McDonald
    June 24th, 2008 at 11:31 #7

    Adam McIntyre — I tested your code in FireFox 3.0 and got 'shift' is not a command. I suppose you're implementing an extension, if so please describe it. Speed test results would be nice too :-)

  8. Rupesh Tiwari
    September 4th, 2010 at 12:18 #8

    Hi Paul,

    It is really a good article that teaches a new idea!
    I learned a lot. Also, I did view your hawtness video on jQuery 1.4

    I viewed your all teaching videos all are very useful. keep it up.

  9. May 20th, 2011 at 05:17 #9

    Very nice post, I surely love this site, keep it up.

  10. June 16th, 2011 at 12:14 #10

    I have a nice utility called whenReady which which create a polling object with a specified condition to satisfy and callback, not necessarily tied to DOM objects.

    Sample usage:

        whenReady({
          cb: function(slowNDumpy) {
            slowAndDumpy.className = 'fast_n_snappy';
          },
          condition: function() {
            var someElem;
            if (someElem = document.getElementById('slowNDumpy')) {
              return someElem;
            }
            return false;
          }
        })();

    Code is @ https://github.com/mawkor2/brandon_js_lib

  11. June 16th, 2011 at 12:18 #11

    Oops, slowAndDumpy line 4 should be slowNDumpy. :P

  12. April 26th, 2012 at 18:03 #12

    Upvote

For code blocks, use <pre lang="javascript">. css and html4strict are also accepted.