Well, even though I can write Javascript fluently, I am not anywhere close to being a Javascript guru. Working mostly on the backend side of the things makes me go back to square -not 1 but maybe 4 or 5- when it is time to write JS again. Today I faced with one of the most common pitfalls about closures in JS. Where closures are magnificent little things that ease asynchronous calls for you, not understanding them fully may result with a bit of pain as well. In short, I must say, closure and loops don’t mix well. And here is why:

Problem

Okay, let’s have a look at this following simple piece of code:

var companions = ['Rose', 'Martha', 'Donna', 'Amy', 'Rory', 'Clara'];

for (var i=0; i<companions.length; i++) {
  window.setTimeout(function(){
    console.log(companions[i]+' is the best!');
  }, 1000);
}

At first glance, you expect that it should claim all the companions are the best right? If things were that way, none of us would be here. Let’s check what we got in our console:

undefined is the best!
undefined is the best!
undefined is the best!
undefined is the best!
undefined is the best!
undefined is the best!

That is interesting enough I hope. I should mention that I think all companions are the best in their own ways. So let’s fix this thing, shall we?

Background

Before dashing into solving our problem, let me give you some background about what is going on here. It’s better to start with the base and build it on top of it.

A scope means the environment of a function, basically everything that it can reach and use.
Lexical scoping (a.k.a. static scoping) is a type of scoping which allows a function to use the environment variables where it is defined.
A closure happens when a function uses a variable out of its immediate lexical scope.

JS uses function level lexical scoping, and what we did in the example above is a closure. Our anonymous function is using ‘companions’ and ‘i’ variables, which are both out of its immediate lexical scope.

Solution

The root of the problem is basically not being careful with closures. What really happens is, before our unsynchronous setTimeouts are being executed, the value of variable i changes. To make it easier to see, I will unfold the for statement with something almost equivalent:

var companions = ['Rose', 'Martha', 'Donna', 'Amy', 'Rory', 'Clara'];
var i = 0
var f0 = function() { console.log(companions[i]+' is the best!'); };
i = i+1 // (i=1)
var f1 = function() { console.log(companions[i]+' is the best!'); };
i = i+1 // (i=2)
var f2 = function() { console.log(companions[i]+' is the best!'); };
i = i+1 // (i=3)
var f3 = function() { console.log(companions[i]+' is the best!'); };
i = i+1 // (i=4)
var f4 = function() { console.log(companions[i]+' is the best!'); };
i = i+1 // (i=5)
var f5 = function() { console.log(companions[i]+' is the best!'); };
i = i+1 // (i=6)

f0();
f1();
f2();
f3();
f4();
f5();

During unfolding, I gave a name to all our anonymous functions, so they are not anonymous functions anymore. As you can see, their definitions are the same, and because of the lexical scoping, they all have access to the variables in global scope. That being said, we are interested in the values of variables in the lexical scope not at the time of defining the functions but running them. So notice the value of i before the function calls. It is 6, which is out of  array. No wonder why it is printing ‘undefined’.

Time to fix it. Here I will give the fixed code first, then explain it:

var companions = ['Rose', 'Martha', 'Donna', 'Amy', 'Rory', 'Clara'];

for (var i=0; i<companions.length; i++) {
  window.setTimeout(function(i){
    console.log(companions[i]+' is the best!');
  }(i), 1000);
}

And check the output:

Rose is the best!
Martha is the best!
Donna is the best!
Amy is the best!
Rory is the best!
Clara is the best!

Yey, looks like we did it! But how did that happen? Instead of using the variable i in global scope, we injected that variable to each functions immediate scope by passing it as a parameter. Since there is a variable i in the immediate scope, the functions does not search outer scopes anymore (called variable shadowing). If you are not familiar with shadowing, it is also possible to do it without shadowing the i variable. You just use a different name for the parameter like:

var companions = ['Rose', 'Martha', 'Donna', 'Amy', 'Rory', 'Clara'];

for (var i=0;i<companions.length;i++) {
  window.setTimeout(function(index){
    console.log(companions[index]+' is the best!');
  }(i), 1000);
}

Before I end the post, I should give a disclaimer. Here this method worked because i is a variable of a primitive type. Primitive types are being copied during function calls where complex types are passed as a reference. If for some reason you need to do this kind of thing with a complex type, you need to find a workaround, like copying it yourself.

Bug-free coding everyone!