Home

Follow Skinkers.

Recieve all the updates from Skinkers via email:

 

Blog.

2

Fixing Internet Explorer’s setTimeout / setInterval scoping issues AND Overcome API’s/Frameworks that don’t allow arguments to be passed to callback methods..

  May 31st, 2011 | JavaScript | Matt Bryson

When using setTimeout or setInterval in IE, there is no way to pass any data to the method you want to execute.
You can obviously create lots of global variables, but this is a very messy solution, and not object oriented.

Similarly, if you are using an API that makes an A-Synchronous call and you can set a callback handler, you may well want to pass extra data to the handler, and the API may not let you.

To get round this you can use a delegate function. You basically tell another function what to execute, what to pass it and the scope to execute it in.

NOTE : This method uses jQuery for browser detection. However you can add whichever browser detection you like and replace the $.browser.mozilla check in the method.

/**
* @param scope Object :  The scope in which to execute the delegated function.
* @param func Function : The function to execute
* @param data Object or Array : The data to pass to the function. If the function is also passed arguments, the data is appended to the arguments list. If the data is an Array, each item is appended as a new argument.
* @param isTimeout Boolean : Indicates if the delegate is being executed as part of timeout/interval method or not. This is required for Mozilla/Gecko based browsers when you are passing in extra arguments. This is not needed if you are not passing extra data in.
*/
function delegate(scope, func, data, isTimeout)
{
	return function()
	{
		var args = Array.prototype.slice.apply(arguments).concat(data);
		//Mozilla/Gecko passes a extra arg to indicate the "lateness" of the interval
		//this needs to be removed otherwise your handler receives more arguments than you expected.
                //NOTE : This uses jQuery for browser detection, you can add whatever browser detection you like and replace the below.
		if (isTimeout && $.browser.mozilla)
			args.shift();	

		func.apply(scope, args);
	}
}


The delegate function then creates another function, that will execute your original function and pass it all your data. This new function requires no arguments and is de-coupled so it does not suffer scoping issues when being passed between objects.



This example shows how to create a delegate method…

//Our function to execute via the delegate
function helloFunc(name, age)
{
	alert("hello " + name + " you are " + age);
}

//Our delegated function
var delegateFunc =  delegate( this, helloFunc,  ["Bob", 50] );

//Now we can execute the delegate function, passing no arguments to it and we see our data alerted.
delegateFunc(); //hello Bob 50.



You can also pass data into the delegate, which will precede the data set when the delegate was created.

//Our function to execute via the delegate
function helloFunc(message, name)
{
	alert(message + name);
}

//Our delegated function
var delegateFunc =  delegate( this, helloFunc,  "Bob" );

//Now we can execute the delegate function, but this time we pass it some arguments. Both these arguments, and our delegated arguments are all passed to the delegated function.

delegateFunc("Welcome to my site"); //Welcome to my site Bob



In the case of setTimeout, you can simply execute the delegate in line as it returns the function reference.

UPDATE: due to Mozilla’s implementation of intervals / timeouts we need to pass a flag to indicate the method is executing as part of a timeout callback. This is due to the fact mozilla will insert an extra parameter indicating the “lateness” of the timeout. More here.

	setTimeout( delegate( this, myFunction,  "hello", true), 5000);



Or in the scope of another object, with multiple arguments as an array

 	setTimeout( delegate( myObject, myFunction,  ["hello", 10, someData], true ), 5000);



When using an API, such as the google gadgets API to load data, you might want to pass extra data through, the delegate works here as well. As it appends arguments, any data passed by the API is still preserved, the delegate will just add your extra arguments to the end of the arguments already passed to the callback.

gadgets.io.makeRequest( myURL , delegate(this, dataSourceLoadedHandler, ["bob", 50]), params);

//Response is passed by the Google API,
//Name and Age are appended to the arguments list by our delegate
function dataSourceLoadedHandler(response, name, age)
{

}

Delegating is a much cleaner and more object oriented approach than littering you code with global variables.

.m

2 Responses to “Fixing Internet Explorer’s setTimeout / setInterval scoping issues AND Overcome API’s/Frameworks that don’t allow arguments to be passed to callback methods..”

  1. Olly_ says:

    Nifty trick! More expert JavaScript tips please. See to be doing more JavaScript than C# these days…

  2. James says:

    Hello ! That’s exactly what I need. I use this function for live result and was crying one more time because of IE. your solution coupled with $.ajaxSetup({ cache: false }); work great for me.

    Thanks a lot (sorry for my broken english). I’ll be back.

Leave a Reply

*