Having been using ES6 for some time now, I've been seemingly immune to the pernicious ES5 related for-loop+async scoping problem. Every Javascript developer has encountered this at some point; but as a reminder, what does the following loop print to the console?

for (var i = 0; i < 5; i++) {  
    setTimeout(function() {
       console.log(i); 
    }, i * 1000);
}
// => ?

It does not print 0...4. Instead it outputs the number 5, five times. Let's briefly review why this is happening before moving into how it's solved in ES5 and ES6 in various ways.

Why? Scope.

Scoping and asynchronous callbacks are the main things causing you to pull your hair out on this type of problem. Specifically, the following:

  1. The variable i in the for loop initializer is global in scope
  2. Our callbacks passed to setTimeout() reference this global i
  3. Because our callbacks are async, the loop completes before any are executed.

Due to the above, when the callbacks do execute, the for loop has already incremented the global variable i to hold the value 5; and we get it repeated in our output since each of the callbacks references the same global i.

for (var i = 0; i < 5; i++) {  
    setTimeout(function() {
       console.log(i); 
    }, i * 1000);
}
//=> 5  5  5  5  5

If you've come to Javascript from a language that has block scoping, this type of hoisting of variables declared in the for loop initializer can definitely be confusing.

So, how do we fix this?

The Fix? Closures!

Prior to ES6, which we'll cover in a bit, the way you solved this was by wrapping the code referencing the global variable i in a closure which would capture the value i at that point in the loop for use by the callback.

For example:

for (var i = 0; i < 5; i++) {  
  (function(n) {
    setTimeout(function(){ 
      console.log(n);
    }, n * 1000);
  })(i);
}
//=> 0 1 2 3 4

Here, we create a closure with an immediately invoked function expression (IIFE), which creates a closure around our code, capturing the value of i in the IIFE's argument to keep it from changing when the callback is executed.

We could also have solved the problem using Function#bind to capture the value of i in a new function with that value applied as the first argument.

for (var i = 0; i < 5; i++) {  
    setTimeout(function(n){ 
      console.log(n); 
    }.bind(null, i)
    , i * 1000);
}
//=> 0 1 2 3 4

Note that we can still use i directly in the second parameter to setTimeout() because that code is executed synchronously each iteration of the loop - meaning i will hold the correct value when evaluated. It's the async callback which executes outside the scope of the loop that caused us problems.

An alternative...

Something else to keep in mind here, is that using Array#forEach (or other Array iteration methods) automatically makes this type of operation safe; because the callback passed to forEach creates a closure around the values in the list via the declared arguments. For instance:

[0,1,2,3,4].forEach(function(n) {
  setTimeout(function(){ console.log(n); }, n * 1000);
});
//=> 0 1 2 3 4

You could implement this with the above code, without using an array literal, by using something like underscore's _.range method to dynamically create the array. But, if you have a list of values in an array, iterate them directly using forEach or using jQuery or underscore's each method, rather than a for loop, to avoid this issue.

ES6 and let to the rescue

With the introduction of let and block scoping in ES6, the previously mentioned problem disappears - so long as you declare your for loop initializers using let instead of var.

for (let i = 0; i < 5; i++) {  
  setTimeout(()=>console.log(i), i*1000);
}
//=> 0 1 2 3 4

Using let in our loop initializer makes i block scoped to the block of code within the loop. This properly captures the value for our callbacks and keeps us from polluting the global scope with for loop initializers all over the place. Not to mention, it keeps the code more readable and concise - nested IIFE's inside for loops don't really add to code clarity or understanding and can be verbose to type.

In summary

This topic has been touched on in a number of resources online previously; but I hope that this post has shed some light on the nature of the issue.

You can find out more about ES6's block scoping in detail on Axel Rauschmayer's execellent post on the subject Variables and Scoping in ECMAScript 6.

You can also read my previous post on Javascript Scope for a more thorough treatment in the general sense, which covers both ES5 and ES6 scoping.