setTimeout Revisited

In my last article, I talked about the problem of passing object parameters to a setTimeout(..) function and how that problem can be solved by using closures. While that method works, I failed to mention that there is an alternative approach if you happen to have the luxury of developing only for the Netscape/Mozilla model (lucky you).

Now, before you stop reading because you think a technique that only works in Mozilla doesn't have much value, let me say that after I explain this technique, I'm going to explore a way to duplicate the technique in IE.

But first things first. Let's explore what setTimeout syntax Mozilla offers us to handle this problem.

Mozilla's version of setTimeout(..) has some additional functionality designed specifically to solve the problem at hand, and it turns out to be tremendously simple and brilliantly elegant (go Mozilla!).

Here's the syntax:


setTimeout(myFunction, nMillisec, [param1, param2, param3, etc…]);

That's it! And you can probably guess what's going to happen here. The comma delimited list of parameters: param1, param2, param3 is going to be passed into the function, myFunction, when that function is called nMillisec milliseconds later.

The brackets, [ and ], indicate that the parameter list is optional. More importantly though, you can pass any number of parameters, and each of them will be "picked up" by the function (in the same order in which they were sent) when it eventually executes — almost like magic!

So this is all well and good if you're using Firefox (or a reasonable faxsmilie), but what if need to support IE?

Well, as we already know, IE offers no such convenient syntax, but there's no reason why we can't write our own, cross-browser version of setTimeout(..) which follows the Mozilla syntax and which works in IE. Since it's a relatively short function, I'll go ahead and post all of the code at once and then discuss it afterwards.


if (!window.sstchur) { window.sstchur = {}; }
if (!sstchur.web) { sstchur.web = {}; }
if (!sstchur.web.js) { sstchur.web.js = {}; }

sstchur.web.js.xb =
{
  setTimeout: function(fnPointer, ms)
  {
    var args = arguments;
    function proxy()
    {
      var params = new Array();
      var i;
      for (i = 2; i < args.length; i++)
        { params.push(args[i]); }

      fnPointer.apply(this, params);
    }

    return window.setTimeout(proxy, ms);
  }
};

I think most of this function is pretty easy to understand, but a few things warrant some extra discussion.

First, the arguments variable. Notice that we haven't defined it anywhere before setting the variable args equal to it, and yet, this is perfectly valid code. This works because every function has an implicit, locally scoped arguments array which is automatically populated with whatever parameters were passed into the function.

So, for example, if variables ab, cd, ef were passed into a function, that function would have an arguments array of length 3: arguments[0] = ab, arguments[1] = cd, and arguments[2] = ef.

Since the length of the arguments array is equal to the number of parameters passed into the function, we can leverage this to receive an arbitrary number of parameters in our setTimeout(..) function.

Next, we create a proxy() function. This is similar to the technique in the last article where our proxy function created a closure for the parameters we needed to send through the setTimeout(..) function. Here again, we're creating a closure — this time, so that we can retain access to the arguments array when our custom setTimeout(..) function goes out of scope.

The arguments array is what we're really interested in, but we can't refer to it directly inside of the proxy() function because proxy() has its own implicit arguments array. For this reason, we keep a reference to the arguments array we're interested in through the code statement: var args = arguments; (notice that is it outside of the proxy() function).

The only other tricky part of our function is the line:


fnPointer.apply(this, params);

The apply(..) function is available to every function reference as a means of executing that function on-demand. It takes 2 parameters: the first parameter is a reference to the object that the function treats as the current object. For most scenerios, using the this keyword will work just fine. The second parameter is an array of values to be passed as parameters to the function, which is exactly what we're aiming to do!

It's important to note that the second parameter we pass to fnPointer is params (not arguments). The params variable is an array created by copying items 2 through arguments.length - 1 from the arguments array.

We need to do this so that we don't end up passing the fnPointer and ms variables as parameters (remember: the arguments array includes all of the parameters passed into a function, whether explicitely defined or not).

Originally, I tried creating the params array without using a loop. I really wanted to use array.slice(..), but it appears that arguments in not truely an array. Surprised? I sure was, but when I ran the statment alert(arguments.constructor.name), it yielded 'object', not 'Array' as I suspected it would. If anyone knows why this is, I'd appreciate your enlightening me. Perhaps I'm missing something obvious. I'd really love to revise this function not to use any loop if possible.

Using the function couldn't be easier:


var xb = sstchur.web.js.xb;

function Person(name, age)
{
  this.Name = name;
  this.Age = age;
}

function sayHello(person)
{
  alert('Hello ' + person.Name + '! You are ' + person.Age + ' years old.');
}

var steve = new Person('Steve', 26);
xb.setTimeout(sayHello, 1000, steve);

The above code will execute the sayHello(..) function, passing in the person object, Steve, as a parameter, after 1 second (1000 milliseconds).

And this works quite well, but there's one thing I should point out. Even though this function works in both IE and Firefox, in the latter, we're not leveraging the browser's inate setTimeout capability. Instead we're overriding it to get the same result, which, to be honest, seems rather silly to me.

If I were to use this code in a production environment, I would most likely revise the function so that in Firefox, it simply leverages the built-in setTimeout function and only in IE would we use the closure concept But I'll leave that as an exersice for the reader 😉

I think this approach is quite a bit better than the one we discussed in the previous blog entry. It has a cleaner syntax, and it relieves the programmer from the task of creating the closure/proxy. The less code a programmer has to write to accomplish something, the less likely he'll make a mistake — definitely a good thing.

I hope this entry has taught you an imporant lesson about cross browser differences: when IE refuses to follow the rules, we beat it into submission!

Comments welcome.

4 Responses

  1. cutie Says:

    i come across this site and find that you are knowledgable in javascript. hope you can help me in solving this problem.

    Is there a way to keep track of the settimeout, so i won't duplicate the the function i am calling? for example,

    if Not IsExist(MyFunctionTimer) {
    setTimeout(MyFunction, 1);
    }

  2. Stephen Stchur Says:

    cutie,

    I will reply to your comment via email.

  3. Joris v/d Wel Says:

    in sstchur.web.js.xb.setTimeout() wouldn't it be better to change the last line:
    window.setTimeout(proxy, ms);
    to
    return window.setTimeout(proxy, ms);
    so one can use window.clearTimeout();

    var blah = sstchur.web.js.xb.setTimeout(fn, 100, 'yay');
    window.clearTimeout(blah);

  4. Stephen Stchur Says:

    Joris,

    Yes, absolutely. In fact, I've already done this in the xb.setTimeout(..) function that I use in my personal projects, but I neglected to update this entry.

    Thanks for pointing this out, I will update it.

Got something to say?

Please note: Constructive criticism is welcome. Rude or vulgar comments however, are not and will be removed during moderation.