0

I have a variable in a loop inside a function:

function myHandler() {
  for (var i = 0; i < items.length; i++) {
    var currItem = items[i];

    myObj.doSomething(function(data) {
      console.log("ok");
      console.log("My currItem id: " + currItem.id); // the last one of all in items
    }, function(e) {
      console.log("error");
      console.log("My currItem id: " + currItem.id); // the last one of all in items
    });
}

currItem.id each time in console.log() is equal to the last of the items in items. Obviously. I've tried to fix this by this:

function myHandler() {
  for (var i = 0; i < items.length; i++) {
    var currItem = items[i];
    var currItem = (function(i2) {
      return items[i2];
    })(i);

    myObj.doSomething(function(data) {
      console.log("ok");
      console.log("My currItem id: " + currItem.id); // the last one of all in items
    }, function(e) {
      console.log("error");
      console.log("My currItem id: " + currItem.id); // the last one of all in items
    });
}

And still got no success. Why and how to fix it?

Mario
  • 185
  • 9
  • @Mario: Wrong; that is exactly your problem. Populating a variable by calling a function doesn't change anything about how that variable is captured. – SLaks Mar 07 '16 at 17:09
  • Yeah, it seems you didn't apply the fix correctly... Easier to just use a `forEach` – elclanrs Mar 07 '16 at 17:10
  • @Mario: Have a look at the other duplicate I linked, where I explained in detail where to place the iife. Does that help? – Bergi Mar 07 '16 at 17:12
  • @SLaks, and that's what I'm asking: how to fix it? open your eyers after all. – Mario Mar 07 '16 at 17:13

2 Answers2

1

Use closure like this:

Inner function which is being returned will remember the environment in which it is created.

function myHandler() {
  for (var i = 0; i < items.length; i++) {
    var currItem = items[i];

    myObj.doSomething((function(currItem) {
      return function(data) {
        console.log("ok");
        console.log("My currItem id: " + currItem.id);
      }
    })(currItem), (function(currItem) {
      return function(e) {
        console.log("error");
        console.log("My currItem id: " + currItem.id);
      }
    })(currItem));
  }
}

Or:

Use [].forEach as each iterator callback will have its own context which will not be overwritten by next iteration..

Rayon
  • 36,219
  • 4
  • 49
  • 76
0

If the doSomething method is async, meaning the callbacks run after the for loop then the currItem will be always the last one.

The reason is closure, the callback functions have a closure which is holding inside all the needed information for the function to execute, including the reference to the variable currItem.

Because it's a reference, when the loop ends the value of currItem is the last item and only then the callbacks are called.

The easiest way to fix it is moving the inside call of the for loop to a function which will accept the variable currItem as a parameter. This way the variable will not change.

You can do it using forEach function of array like so:

items.forEach(function(currItem){
....
});
Slava Shpitalny
  • 3,965
  • 2
  • 15
  • 22