7

The code has an issue that the variable gets over written when the asynchronous function is called. How can it be fixed?

Code:

for (x in files) {

 asynchronousFunction(var1, var2, function(){
      console.log(x.someVaraible);
  }); 

}

Now the issue is that when the callback function in the asynchronousFunction is called the x.files variable has been updated to the next varaible in the json array files. I want that the variable should contain the previous value.

The number of variables in the callback function can not be changes, so variable name can not be sent in the call back function.

caitriona
  • 8,569
  • 4
  • 32
  • 36
S. A. Malik
  • 3,465
  • 6
  • 37
  • 56
  • Is `x.someVaraible` always printing the last value? – gen_Eric Jul 31 '12 at 19:05
  • 1
    This code is probably broken. `x` will always be a string, not an array element (which appears to be what you're going for). – cHao Jul 31 '12 at 19:06
  • @Rocket: exactly it is always printing the last value. – S. A. Malik Jul 31 '12 at 19:06
  • @cHao: No it is not always a string , it is a Json object. – S. A. Malik Jul 31 '12 at 19:07
  • 1
    There's no such thing as a JSON object. JSON is a text format. You have JSON, and you have objects. And yes, `x` will *always* be a string, and almost certainly not the one you were expecting, because `for`...`in` loops over property *names*, not *values*. (Side note: people shouldn't be using `for`...`in` in most of the cases where they do. This being a prime example.) – cHao Jul 31 '12 at 19:08

4 Answers4

14

The problem with using 'local' variables in javascript is that your variables actually have function scope, rather than block scope - like Java and C# etc has.

One way to get around this is using let which has block scope, but only firefox supports this currently.

So this code would work in firefox only:

for (var x in files) {
  // This variable has BLOCK scope
  let file = files[x];
  asynchronousFunction(var1, var2, function(){
     console.log(file.someVariable);
  }); 
}

For other browsers, the alternative is to use closures

for (var x in files) {
  var file = files[x];
  asynchronousFunction(var1, var2, (function(file){
      return function() {
                console.log(file.someVariable);
             };
  })(file); 
}

Another way you could do this is using map/forEach, assuming that the datatype of files is an array.

files.forEach(function(file) {
     asynchronousFunction(var1, var2, function(){
                console.log(file.someVariable);
             });
});

If it's not an array, then you can always use this technique

 [].forEach.call(files, function(file) {
     asynchronousFunction(var1, var2, function(){
                console.log(file.someVariable);
             });
});

The fuller way of writing this is of course

Array.prototype.forEach.call(files, function(file) {
     // As before

But I feel [].forEach is nicer on the eyes.

manneJan89
  • 1,004
  • 8
  • 27
AlanFoster
  • 8,156
  • 5
  • 35
  • 52
  • 1
    lol, +1 for using the actual object value haha. I don't even care about other mistakes at this point. – Esailija Jul 31 '12 at 19:15
  • 1
    Answer is correct but explanation is wrong. The problem is not that `varaibles have function scope` but rather that a closure is created when a variable is shared between two functions. What you want to do is to break the closure. Fortunately javascript passes arguments by copy of reference. Since it copies the reference instead of passing the reference itself the closure is broken. That's why the method of using function arguments work. – slebetman Jul 31 '12 at 19:21
  • "Passes by copy of reference"? Kinda a conflation of things. JS passes by value. As in, by copy. Always. Just so happens that the "value" of an object variable is a reference to an object. – cHao Jul 31 '12 at 19:24
  • Now that we are actually using values and the scoping is handled, can we use `for` loop since it's an array? – Esailija Jul 31 '12 at 19:25
  • @Esailija I'm not sure I understand your question. Do you mean a `forEach` loop? files.forEach(...) ? – AlanFoster Jul 31 '12 at 19:27
  • @cHao: There is a slight distinction there. Languages that 'really' pass by value (TCL) actually copies the object passed. Javascript doesn't do this. Rather, it copies only the reference to the object. That is why I call it `by copy of reference`. – slebetman Jul 31 '12 at 19:28
  • @AlanFoster `forEach` would be even better since it gives the closure for free, but really since it's an array, `for..in` is just wrong. By `for` I mean a normal plain `for` loop. as in `for( var i = 0; i < files.length; ++i )` – Esailija Jul 31 '12 at 19:28
  • Thanks a lot, you where of a great help, and thanks for all the other explanations. – S. A. Malik Jul 31 '12 at 19:28
1

You need to disentangle the x from the closure created by the callback function. Short answer:

for (x in files) {
    asynchronousFunction(var1, var2,
        (function(y){
            return function(){
                console.log(y.someVaraible);
            }
        })(x)
    ); 
}

For a longer answer and explanation see my answer to a previous question: Please explain the use of JavaScript closures in loops

Community
  • 1
  • 1
slebetman
  • 109,858
  • 19
  • 140
  • 171
0

You need to either bind the variable to the function definition, or pass the variable into the function. I'd probably take a combination approach, something like:

for (x in files) {
    var local_x = x;
    var fn = function () {
         theRealFunction( local_x );
    };
    asynchronousFunction(var1, var2, fn);
}

Although in your example I wouldn't need to call theRealFunction() as its such a small amount of code (just console.log).

Robert
  • 2,441
  • 21
  • 12
0

X gets iterated so by the time the callback function is called, x will be the last iterated function from the loop.

Fix by making x local like this:

for (x in files) {
    (function() {
        var x = arguments[0];
        asynchronousFunction(var1, var2, function() {
            console.log(x.someVaraible);
        });
    }(x));
}
Esailija
  • 138,174
  • 23
  • 272
  • 326
Silviu-Marian
  • 10,565
  • 6
  • 50
  • 72