Today's programmatic venting is brought to you by Internet Explorer's implementation of the setTimeout() function under Javascript.
What is setTimeout()?
setTimeout() schedules an arbitrary function call for some point in future. This is useful when you have functions that do repetitive tasks some milliseconds apartment, but not constantly.
The reason why this is used instead of a simply while (true) { ... } loop is because Javascript is a single-threaded language. So if you tie up the interpreter by executing one piece of code over and over, nothing else in the browser gets a chance to run. setTimeout() allows other pieces of Javascript (or browser events, such as clicking on a button) to run, while guaranteeing that your code will be executed at some point.
Sounds cool. How do I use setTimeout()?
Simple, like this:
setTimeout(function_name, msec);
This will cause a function called function_name to execute a certain number of milliseconds in the future.
For functions that don't need data passed into them, that works great. But what if you want to pass in parameters? In FireFox, you would do this:
setTimeout(function_name, msec, arg1);
And this works great. It will call the function called function_name again in the specified number of milliseconds. Except, MSIE doesn't like this. And rather than throw an error, it just ignores the additional arguments. So when function_name gets called again, its argument is now undefined, and choas ensues.
[Edit: It has since been pointed out to me that the most recent draft of the spec does not allow for additional parameters to setTimeout(). Okay then, but why isn't MSIE at least throwing an error? Silently accepting additional arguments and then just throwing them away is just plain stupid.]
So how do I work around this annoyance?
Closures. Up until now, I thought that closures were very geeky, and used only by CS people who were working on their PhDs. Then I saw this:
setTimeout(function() {function_name(arg1)}, msec);
Wait, what? What's going on there? Well, it might be a little easier to understand if I write it like this:
var f = function() {function_name(arg1); };
setTimeout(f, msec);
What's happening there is a variable called "f" is created, which is actually an anonymous function. That function in turn contains a single line of code: function_name(arg1); -- a call to the function we really want to call, with our specified argument. Note that all we have done so far is define a function that calls our target function. We have not executed either that function or the target function -- the target function is essentially "frozen in time". This is a very important concept, and if you've never done this before, you will have difficulty wrapping your brain around it. :-)
To get a better idea of what's going on, in this case, you could also execute the variable f as a function, since it now *is* a function for all intents and purposes:
f();
Bringing this all together, in the above example, the end result is that the function function_name will be executed after the specified interval, with the desired argument. And this will work in both MSIE and FireFox.
Just out of curiosity, what is a practical application of this function?
I'm glad you asked! As I mentioned above, Javascript is single-threaded. This means that a loop that takes a long time to run can cause the browser to appear to "freeze up" or become unresponsive, which detracts from the overall user experience. A perfect example of this would be populating a div with rows of data retrieved via AJAX. Writing 1,000 rows into that div takes about 3.2 seconds on my machine, during which the browser will not respond to commands.
What I did in my little test was instead of printing each row as it was retrieved, I instead stored them in an array. I then wrote a function which shift()ed and printed only 50 elements from that array. After 50 elements were printed, it then checked to see if there were any elements left in the array. If there were, it would call setTimeout() with itself and the array inside a closure, to be run again in 10 millseconds. The end result took a little longer, 3.5 seconds, but resulted in a much more responsive browser, and happier users.
Share and enjoy!
Source: Passing parameters to a function called with setTimeout, by Bryan Gullen
What is setTimeout()?
setTimeout() schedules an arbitrary function call for some point in future. This is useful when you have functions that do repetitive tasks some milliseconds apartment, but not constantly.
The reason why this is used instead of a simply while (true) { ... } loop is because Javascript is a single-threaded language. So if you tie up the interpreter by executing one piece of code over and over, nothing else in the browser gets a chance to run. setTimeout() allows other pieces of Javascript (or browser events, such as clicking on a button) to run, while guaranteeing that your code will be executed at some point.
Sounds cool. How do I use setTimeout()?
Simple, like this:
setTimeout(function_name, msec);
This will cause a function called function_name to execute a certain number of milliseconds in the future.
For functions that don't need data passed into them, that works great. But what if you want to pass in parameters? In FireFox, you would do this:
setTimeout(function_name, msec, arg1);
And this works great. It will call the function called function_name again in the specified number of milliseconds. Except, MSIE doesn't like this. And rather than throw an error, it just ignores the additional arguments. So when function_name gets called again, its argument is now undefined, and choas ensues.
[Edit: It has since been pointed out to me that the most recent draft of the spec does not allow for additional parameters to setTimeout(). Okay then, but why isn't MSIE at least throwing an error? Silently accepting additional arguments and then just throwing them away is just plain stupid.]
So how do I work around this annoyance?
Closures. Up until now, I thought that closures were very geeky, and used only by CS people who were working on their PhDs. Then I saw this:
setTimeout(function() {function_name(arg1)}, msec);
Wait, what? What's going on there? Well, it might be a little easier to understand if I write it like this:
var f = function() {function_name(arg1); };
setTimeout(f, msec);
What's happening there is a variable called "f" is created, which is actually an anonymous function. That function in turn contains a single line of code: function_name(arg1); -- a call to the function we really want to call, with our specified argument. Note that all we have done so far is define a function that calls our target function. We have not executed either that function or the target function -- the target function is essentially "frozen in time". This is a very important concept, and if you've never done this before, you will have difficulty wrapping your brain around it. :-)
To get a better idea of what's going on, in this case, you could also execute the variable f as a function, since it now *is* a function for all intents and purposes:
f();
Bringing this all together, in the above example, the end result is that the function function_name will be executed after the specified interval, with the desired argument. And this will work in both MSIE and FireFox.
Just out of curiosity, what is a practical application of this function?
I'm glad you asked! As I mentioned above, Javascript is single-threaded. This means that a loop that takes a long time to run can cause the browser to appear to "freeze up" or become unresponsive, which detracts from the overall user experience. A perfect example of this would be populating a div with rows of data retrieved via AJAX. Writing 1,000 rows into that div takes about 3.2 seconds on my machine, during which the browser will not respond to commands.
What I did in my little test was instead of printing each row as it was retrieved, I instead stored them in an array. I then wrote a function which shift()ed and printed only 50 elements from that array. After 50 elements were printed, it then checked to see if there were any elements left in the array. If there were, it would call setTimeout() with itself and the array inside a closure, to be run again in 10 millseconds. The end result took a little longer, 3.5 seconds, but resulted in a much more responsive browser, and happier users.
Share and enjoy!
Source: Passing parameters to a function called with setTimeout, by Bryan Gullen
(no subject)
Date: 2007-12-18 07:41 pm (UTC)(no subject)
Date: 2007-12-18 07:43 pm (UTC)They are the heart of jQuery (http://www.jquery.com/) and part of what makes it so powerful.
(no subject)
Date: 2007-12-18 07:52 pm (UTC)In retrospect, my initial comment (as explicited herein) was rather snide and consequently rude, and I shouldn't have posted it. I'll let you delete the whole thread if you want.
(Yeah, so I'm ultra bitter about this whole goddamn industry, can you tell?)
(no subject)
Date: 2007-12-18 07:56 pm (UTC)That being said, I don't think closures are anywhere near basic.
(no subject)
Date: 2007-12-18 09:07 pm (UTC)This being said, it's Firefox that's breaking the spec here. (Apparently the Mozilla organization stopped caring about, you know, standards, at some point around the time they started rolling around in money. Interestingly, this is also the time they started spending real money on marketing, and turned FF into one of the most successful open source projects ever. Nowadays, Firefox has replaced IE as the browser whose incompatibilities and bugs you are supposed to emulate if you don't want to be considered 'broken'. I so want out of this fucking industry.)
(no subject)
Date: 2007-12-18 09:10 pm (UTC)"Publication as an Editor's Draft does not imply endorsement by the W3C Membership. This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than work in progress."
Seeing that it's 1.5 years old, is there anything newer out there?
(no subject)
Date: 2007-12-18 09:47 pm (UTC)Not that it's the only Firefox 'innovation' out there, as far as I can tell. (My source for this statement is the commit logs of some other browser engines, in which I've noted a number of "Emulate Firefox bug/incompatibility..." entries. No, I don't have any link to provide right away, and I'm not going to hunt one down. I'm not out to try and convince you, I just feel that I have to justify myself, and to point out that my comment was not gratuitous or unreasoned.)
(no subject)
Date: 2007-12-18 09:49 pm (UTC)I've heard other complaints about FireFox, and I think they do have some explaining to do.
However, if the spec doesn't allow for functions to be passed into setTimeout(), then I really have to ask what they were thinking. Forcing people to clean closures just to pass in data (or resort to using globals) is not going to promote good programming practices.
(no subject)
Date: 2007-12-18 11:03 pm (UTC)(no subject)
Date: 2007-12-18 11:40 pm (UTC)It's not
setTimeout( function_name, msec )
setTimeout recieves a function pointer, not the name. Conveniently, the function name is the same as a variable with the name of the function, but like you pointed out above, you can also do
var foo = function(){...}
or even
function foo()
{
}
...
function bar()
{
var fooPointer = foo;
}
There is still a problem with your technique, which I don't fully understand, but I've had to deal with...
IE makes a copy of the call stack whenever you define a function like you did above, so it's not a cheap operation to perform... and, worse of all, sometimes (I don't remember exactly when, but google probably does), IE won't free that memory, there's some circular reference somewhere and the GC will keep the n copies of the callstack you made around forever. Watch IE's memory consumption before and after your call to see if you're affected.
Another, much less elegant solution to the problem, that sometimes doesn't even cut it, is to abuse Javascript:
setTimeout( "functionName(" + arg1 + "," + arg2 ")", msecs );
That is, to create a string with the function call the way you want it to look. It's awful, but it doesn't suffer from IE's memory leaking problem.
(no subject)
Date: 2007-12-18 11:42 pm (UTC)How exactly did you deal with the problem? Did you just not use this approach, or did you modify it?
> setTimeout( "functionName(" + arg1 + "," + arg2 ")", msecs );
That only works for strings and ints. Unless I'm missing something, that will completely fall apart when trying to operate on arrays and objects. (And yes, I was working with a large array in my case)
(no subject)
Date: 2007-12-18 11:46 pm (UTC)http://www.w3.org/TR/Window/#window-timers
It has 2 parameters there.
Firefox seems to have addressed most of IE's bugs and fixed them... but they have their own share of quirks and bugs that are sometimes very annoying. I've been working lots with it.
Though the alpha of the next major release of Firefox (are they releasing a minor release every other week now?) seems to fix a number of them :)
(no subject)
Date: 2007-12-18 11:48 pm (UTC)(no subject)
Date: 2007-12-18 11:54 pm (UTC)In some cases we lived with the memory leak and left a note to revisit that in the future...
The future is now, but we're so busy that we're not revisiting that just yet. The only instances where we left this sort of thing in place happen only once at page boot, and not too many times (that is, setTimeout doesn't get called that many times), so even if we leave them there they could be considered part of our memory footprint rather than leaking memory.
(no subject)
Date: 2007-12-18 11:57 pm (UTC)> that in the future...
HAHAHA! FAMOUS LAST WORDS.
Ahem. Anyway... :-)
Member variables are good. It's a shame that Javascript's OO syntax is so different from PHP, Java, and the like. If it were less functional-like and prototype-based, there would be a lot more OO going on and less reason to use closures...
(no subject)
Date: 2007-12-19 12:13 am (UTC)I believe the Google webkit does the same for Java instead of C#, that may be more interesting for you.
And, actually, before we ship most of these Future notes are converted into bugs and have to be fixed or officially punted ;P
(no subject)
Date: 2007-12-19 12:15 am (UTC)(no subject)
Date: 2007-12-19 12:20 am (UTC)What?
If you're referencing something, it's WAY over my head.
(no subject)
Date: 2007-12-19 12:42 am (UTC)(no subject)
Date: 2007-12-19 08:07 pm (UTC)They are also important when working with the XMLHttpRequest Object when passing in the event handler function when you are running multiple XMLHttpRequest's simultaneously and you want to know which xml object is being called back. They are teh wonderful!
(no subject)
Date: 2007-12-19 08:09 pm (UTC)