-1

I have following code:

var i;

for (i = 1; i <= 10; i++) {
    console.log(i);
}

This obviously prints numbers from 1 to 10 just fine. Then I want to do this asynchronously:

var i;

for (i = 1; i <= 10; i++) {
    setImmediate(function() { console.log(i); });
}

And here it is — it prints only 11-s. First I was wondering if this is some shared state problem (it happened when I tried to fetch multiple pages with PhantomJS, and ended fetching the same one N times, so I've thought something is not quite async-safe there), then compacted the issue up to example shown above, then understood it's because javascript closures don't seem to capture their environment — so despite the fact I used i in function, it uses actual value of i at the moment of execution, which is, indeed, 11.

Unfortunately, due to design of API, I'm unable to pass additional parameters to callbacks to make i local to function, as I can't change the signature.

So, question is — how to rewrite second example to work correctly?

toriningen
  • 7,196
  • 3
  • 46
  • 68

2 Answers2

1

Use an annonymous wrapper for this:

for(var i = 0; i <= 10; i++) {
    (function(e) {
        setTimeout(function() {
            console.log(e);  
        }, 1000);
    })(i);
}

The anonymous outer function gets called immediately with i as its first argument and will receive a copy of the value of i as its parameter e. The anonymous function that gets passed to setTimeout now has a reference to e, whose value does not get changed by the loop.

Another way of doing this is:

for(var i = 0; i <= 10; i++) {
    setTimeout((function(e) {
        return function() {
            console.log(e);
        }
    })(i), 1000)
}
pj013
  • 1,393
  • 1
  • 10
  • 28
  • Thanks, this seems much better than binding value to `this`, as `this` itself may be useful. – toriningen Apr 14 '14 at 19:49
  • @modchan: Doesn't matter. If you don't bind `this`, then it will just get the default value. A function doesn't keep the `this` value of its enclosing environment, unless you're using ECMAScript 6 fat arrow function syntax. So if you need a useful `this`, you might as well use `.bind()`. – cookie monster Apr 14 '14 at 19:58
  • @cookiemonster, sorry, my fault. – toriningen Apr 14 '14 at 20:02
1

To understand this problem, you need to realize that a closure captures the variable itself not the value of the variable at the time the function is created. Therefore, every function created inside the loop closes over the same i (whose value is 11 at the end of the loop).

In order to have each function close over a its own variable, you need to introduce a new scope, which in javascript is done using functions. Like this:

for (i = 1; i <= 10; i++) {
    (function(c) {
        setImmediate(function() { console.log(i); });
    })(i);
}

Now i is scoped to its nearest containing function (which is the same for each iteration) and the parameter c is scoped to its nearest containing function (which is a new function during each iteration).

Wayne
  • 59,728
  • 15
  • 131
  • 126