4

I have this code :

for (var i = 0; i < result.length; i++) {
    // call a function that open a new "thread"
    myObject.geocode({ param1: "param" }, function(results, status) {
        alert(result.title[i]);
    });                                             
}

The .geocode function (that is not mine, so I can't edit) open a new "thread" of execution.

When I try to print title on each step, I get always the last possible value of i.

How can I keep a reference to the right value of i for each iteration?

Matt
  • 74,352
  • 26
  • 153
  • 180
markzzz
  • 47,390
  • 120
  • 299
  • 507

2 Answers2

5

You can create a closure within the loop;

for (var i = 0; i < result.length; i++) {
    // call a function that open a new "thread"
    (function (i) {
        myObject.geocode({ param1: "param" }, function(results, status) {
            alert(result.title[i]);
        });
    }(i));                                             
}

So here we're creating a function;

(function (i) {
    myObject.geocode({ param1: "param" }, function(results, status) {
        alert(result.title[i]);
    });
});

... which accepts one parameter named i, and launches the geocode request. By adding the (i) to the end of the declaration of a function expression, we run the function straight away and pass it the current value of i.

(function (i) {
    myObject.geocode({ param1: "param" }, function(results, status) {
        alert(result.title[i]);
    });
}(i));

It doesn't matter that a variable i already exists at a higher scope than the closure, because the local declaration of i overrides it. Either the variable we pass to the closure, or the name the closure calls the variable could be different;

(function (anotherVariable) {
    myObject.geocode({ param1: "param" }, function(results, status) {
        alert(result.title[anotherVariable]);
    });
}(aVariable));

Alternately you could also extract the logic to another function (I prefer it, but it's less cool):

function geocode(i) {
   myObject.geocode({ param1: "param" }, function(results, status) {
       alert(result.title[i]);
   });
}

for (var i = 0; i < result.length; i++) {
    geocode(i);                                            
}

The problem is down to the same i variable being used by the callback functions; which, as you've discovered, has moved on by the time the callback executes. Both of the above solutions creates another variable for each iteration, and it is this that the callback operates on.

Matt
  • 74,352
  • 26
  • 153
  • 180
  • Why do you put "(i)" at the end of the closure (function (i)? – markzzz Feb 07 '12 at 16:36
  • @markzzz: It invokes the function straight away and passes the current value of `i` to it. Inside the closure `i` is the local parameter which has just been passed, hence the value of `i` does not change. – Matt Feb 07 '12 at 16:37
  • Uhm...sorry...I don't understand what kind of syntax is `(function (i) { } (i));`. Is it that `(i)` that invoke `(function (i) { })`? :O – markzzz Feb 07 '12 at 16:41
2

See JavaScript closure inside loops to understand why your code doesn't work.

for (var i = 0; i < result.length; i++) {
    var callback = (function(i) {
        return function(results, status) {
            alert(result.title[i]);
        };
    })(i);

    myObject.geocode({ param1: "param" }, callback);
}
Community
  • 1
  • 1
Sophie Alpert
  • 139,698
  • 36
  • 220
  • 238
  • The `i` variable within the function whose result is assigned to `callback` variable, is `undefined`. And because of that your code actually does something like that: `alert(result.title[undefined])`. – Tadeck Feb 07 '12 at 16:30
  • Sorry, missed an `i`. Thanks for the heads-up! – Sophie Alpert Feb 07 '12 at 16:31