Thursday, July 12, 2012

Javascript Question

Javascript has a setTimeout function that accepts a callback function and a millisecond interval to wait before executing the callback function.  There are many different ways to pass the callback function:

1. You can pass the function pointer: setTimeout(yourCallbackFunction, 10);


2. You can pass an anonymous function: setTimeout(function(){your code here;}, 10);


3. You can use a closure to pass in a multiple parameters call: setTimeout({yourCallBackFunc(parm1, parm2)}, 10);


4. You can accomplish much the same end by:

var f = function(){yourCallbackFunc(parm1, parm2)};
setTimeout(f, 10);


5. You can pass a string:

var str = "yourCallbackFunc(" + parm1 + "," + parm2 + ")";
setTimeout(str, 10);


Yes, this seems incredibly sloppy, and while it works when this Javascript is inside an HTML page, it does not work when you are feeding the Javascript into Node.js.  (If you don't know what Node.js is -- it is the new cool way to write server code, or, according to others, a form of web cancer.)  This fourth scheme does not work in Node.js because it isn't object-oriented.

So, why not use method 3?  It is more elegant anyway.  Unfortunately, there is something rather odd that is happening when I use method 3.  There are multiple calls being made to setTimeout with different parameters passed.  But when the callback function executes at the end of the timeout interval, it always has the same parameters: the ones specified on the last call.  My guess is that because this is single-threaded (and for various reasons, I can't create multiple threads), the closure is creating a single copy of {yourCallBackFunc(parm1, parm2)}, and by the time the first callback function is called, the most recent copy is what exists, and is used by each invocation.

Any suggestions?  I am thinking of creating an array of var f as in option 4 above, but that seems wasteful.

UPDATE: I think I found the solution.

setTimeout(yourCallBackFunc, 10, parm1, parm2);


I don't know why the more elegant closure solution doesn't work.

1 comment:

Tim Henderson said...

Solution #3 doesn't work as you expect because the closure is referencing the variables parm1 and parm2 in the parent scope, not creating copies of them. Thus, invocations of your callback function will see the current value of parm1 and parm2, not the value they had when setTimeout was called. As you guessed, by the time the callbacks start happening the calls to setTimeout have all been made, so parm1 and parm2 have the values they had on the last call. One way to fix this would be to encapsulate the call to setTimeout in its own function:


function createCallback(parm1, parm2) {
window.setTimeout(function() {myCallbackFunc(parm1, parm2);}, timeout)
}

Each call to the function will create its own closure with the current values of parm1 and parm2, so myCallbackFunc() will get the values you expect.