Reputation: 14282
Suppose we have a list of simple objects:
var things = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
{ id: 3, name: 'three' }
];
And we need to iterate through these objects and register them as parameters for some later event. The naive approach shares the same object reference between all callbacks, so each fires against the last item:
for (var i = 0; i < things.length; i++) {
var o = things[i];
setTimeout(function() { doSomethingWith(o); }, i * 1000);
}
A typical solution is to create a closure, limiting the scope of our object reference:
for (var i = 0; i < things.length; i++) {
(function() {
var o = things[i];
setTimeout(function() { doSomethingWith(o); }, i * 1000);
})();
}
If we're not targeting IE<9, we could depend on .forEach()
:
things.forEach(function(o, i) {
var o = things[i];
setTimeout(function() { doSomethingWith(o); }, i * 1000);
});
But, we've ultimately created a sort of closure in this case anyway in passing an anonymous function to forEach()
.
Is there a way to accomplish this without a closure or function reference?
The underlying problem is three-fold:
setTimeout()
(or whatever it may be) makes you (me) feel like you're creating a closure. So, I have a tendency to forget the outer closure..forEach()
in IE>9, unless the application or organizational style guide dictates a newline + indent for the closure.Maybe the better way to ask it is this: What the devil did we all do before we all started compulsively creating closures?
Upvotes: 0
Views: 71
Reputation: 102
I figure it out, is 2 different ways. This first one, you bind the parameter to the call of this method. It clones the parameter things[i] inside the function and uses it as parameter.
for (var i = 0; i < things.length; i++) {
var o = things[i];
setTimeout(doSomethingWith.bind(null, things[i]), i * 1000);
}
and in a second way,
setTimeout after the parameter time in ms, accept the parameters from the function that you will call, it also copy the values at the moment it was defined, so the variable value can change after and setTimeout will guarantee that the right value will be passed as parameter.
for (var i = 0; i < things.length; i++) {
var o = things[i];
setTimeout(function(param) { doSomethingWith(param); }, i * 1000, o);
}
Hope it helps!
Upvotes: 1
Reputation: 664538
I don't think there's anything wrong with using closures here. They are the natural tool in javascript, and are rather necessary for asynchronous callbacks with local states - as we want to avoid global state.
If you care that much about indentation, you can put the scope-providing IEFE on the same line as the loop:
for (var i = 0; i < things.length; i++) (function() {
var o = things[i];
setTimeout(function() { doSomethingWith(o); }, i * 1000);
}());
Otherwise, you've already used forEach
perfectly fine. Notice that you don't need to care about IE<=8 in your code, as forEach
is trivially shimmable if you want to support it.
And of course, ES6 will give us let
statements that solve this very common problem with a new syntax - you'll need to use a 6to5-transpiler though:
for (let i = 0; i < things.length; i++) {
// ^^^
var o = things[i];
setTimeout(function() { doSomethingWith(o); }, i * 1000);
}
If you want a very clear code organisation, make the closure explicit:
function makeDoer(thing) {
return function() { doSomethingWith(thing); };
}
for (var i = 0; i < things.length; i++) {
setTimeout(makeDoer(things[i]), i*1000);
}
What the devil did we all do before we all started compulsively creating closures?
We used global state, and solved our problems differently. For example, your case would tackled by a semi-recursive function much better:
var i = 0;
function next() {
if (i < things.length) {
doSomethingWith(things[i++]);
setTimeout(next, 1000);
}
}
next();
Upvotes: 1