giza: Giza White Mage (Default)
[personal profile] giza
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

(no subject)

Date: 2007-12-18 07:41 pm (UTC)
From: [identity profile] balinares.livejournal.com
This is just about why I despise Web programming. >_<

(no subject)

Date: 2007-12-18 07:43 pm (UTC)
From: [identity profile] giza.livejournal.com
Don't be talking smack about anonymous functions. ;-)

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)
From: [identity profile] balinares.livejournal.com
... What I despise about Web programming is that this kind of idiom is somehow considered magical or out of the ordinary. I mean, what the hell happened to knowing the basics of your trade, people?

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)
From: [identity profile] giza.livejournal.com
Compared to stuff that's gone down in this LJ, you're a whole continent away from rude. :-)

That being said, I don't think closures are anywhere near basic.
Edited Date: 2007-12-18 07:57 pm (UTC)

(no subject)

Date: 2007-12-18 09:07 pm (UTC)
From: [identity profile] balinares.livejournal.com
Dunno. From the moment you start thinking of functions as first class objects, passing them around like any other object is natural; and from there, passing them around with a bound scope is no big stretch. But eh, you're probably right. Snide, as said. Bitter.

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)
From: [identity profile] giza.livejournal.com
You sure that's a spec? The document states, in part:

"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)
From: [identity profile] balinares.livejournal.com
This is the newest specification I could find, and I spent a while searching. (Somehow I suspected you'd question its validity. *g*) You'll note that the URL points to a CVS, and this document is the very latest revision. The currently published spec is here and might be older, but it has the same API for setTimeout() as the latest CVS revision.

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)
From: [identity profile] giza.livejournal.com
That's cool.

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)
From: [identity profile] balinares.livejournal.com
That's the W3C. What were you expecting? :)

(no subject)

Date: 2007-12-18 11:40 pm (UTC)
From: [identity profile] furahi.livejournal.com
I've seen the prototype for setTimeout written like that... I think it contributes to the confusion
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)
From: [identity profile] giza.livejournal.com
Yes, I am aware of the memory leaking, but maybe that's because I tend to think about allocation as things go in and out of scope in the back my head. In this case, the arguments in the closure never really go out of scope (so long as that section of code is running), so yeah...

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)
Edited Date: 2007-12-18 11:43 pm (UTC)

(no subject)

Date: 2007-12-18 11:46 pm (UTC)
From: [identity profile] furahi.livejournal.com
This is the standard, isn't it?
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)
From: [identity profile] furahi.livejournal.com
(I was originally looking for the ECMA standard, since Javascript is an ECMA standard, not a W3C standard... however, ECMA just describes the language, these functions come from the HTML DOM, not from Javascript)

(no subject)

Date: 2007-12-18 11:54 pm (UTC)
From: [identity profile] furahi.livejournal.com
In some cases we used namespace-wide variables to help with the issue, or member variables (we put the parameters in the class of the object that needs to be updated, and null the member variable at the end), in some we used the string hack (it's a hack, not a workaround in my opinion :P)...
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)
From: [identity profile] giza.livejournal.com
> In some cases we lived with the memory leak and left a note to revisit
> 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)
From: [identity profile] furahi.livejournal.com
We're actually switching almost everything to Script#, it is soooooooooo nice.
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)
From: [identity profile] taral.livejournal.com
... baby steps. Just baby steps. Soon you'll be snacking on higher-order functions!

(no subject)

Date: 2007-12-19 12:20 am (UTC)
From: [identity profile] giza.livejournal.com

What?

If you're referencing something, it's WAY over my head.

(no subject)

Date: 2007-12-19 12:42 am (UTC)
From: [identity profile] taral.livejournal.com
No, no references. :)

(no subject)

Date: 2007-12-19 08:07 pm (UTC)
From: [identity profile] oryx.livejournal.com
I discovered function closures when working with the Google Maps API, I found that I needed to pass objects as arguments that are only available at the time that the even was called from Google Maps.

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)
From: [identity profile] giza.livejournal.com
XMLHttpReequest? Um, isn't that what AJAX libraries are for?

Profile

giza: Giza White Mage (Default)
Douglas Muth

April 2012

S M T W T F S
1234567
891011121314
15161718192021
22232425262728
2930     

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags