Javascript has somewhat strange scoping rules that can catch you unaware. Here’s a typical example:
function test() {
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
}
test();
This prints “0 1 2 3 4“, each number after a second, right? Wrong. What it actually outputs is “5 5 5 5 5“. Why? Because the callback function keeps a reference to i, which gets incremented to 5 long before any of the calls are made.
This is unfortunate because setting up a callback from a loop is often used in Javascript. How to work around this? There are basically two methods.
The first is wrapping the callback setup within another anonymous function, and then passing the variable as a parameter, which will effectively copy it:
for (var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() {
console.log(i);
}, 1000);
})(i);
}
It works, but is rather ugly.
The second way is to add a parameter to the callback, and then curry the parameter, so it’s immediately bound and the value copied (thanks to @mreow for pointing it out). This achieves more or less the same effect as before, but is hopefully a bit more readable:
for (var i = 0; i < 5; i++) {
setTimeout((function(n) {
console.log(n);
}).bind(undefined, i), 1000);
}
Hm. Right. Not much more readable. But, we can improve on it by creating a helper method (which will, presumably, be useful beyond this one case, so the added complexity is worth it):
function curry(val, fn) { return fn.bind(undefined, val); }
function test() {
for (var i = 0; i < 5; i++) {
setTimeout(curry(i, function(n) {
console.log(n);
}), 1000);
}
}
test();
Now, that’s hopefully a bit cleaner. The curry function binds the value as the first parameter (the undefined there is because we don’t bind it to an object – see the bind documentation for details).
Currying is probably confusing at first sight, so here’s another example:
function curry(val, fn) { return fn.bind(undefined, val); }
function plus(a, b) { console.log(a + b); }
var plus2 = curry(2, plus);
plus2(3);
We first create a function that adds two numbers. Then, we curry that function binding value of “2″ as the first argument, and get another function, “plus2″. Then, we can call that function providing only the second argument. The end result is “5″.