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.

5
  • Is x.someVaraible always printing the last value? Commented Jul 31, 2012 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). Commented Jul 31, 2012 at 19:06
  • @Rocket: exactly it is always printing the last value. Commented Jul 31, 2012 at 19:06
  • @cHao: No it is not always a string , it is a Json object. Commented Jul 31, 2012 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.) Commented Jul 31, 2012 at 19:08

4 Answers 4

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.

Sign up to request clarification or add additional context in comments.

8 Comments

lol, +1 for using the actual object value haha. I don't even care about other mistakes at this point.
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.
"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.
Now that we are actually using values and the scoping is handled, can we use for loop since it's an array?
@Esailija I'm not sure I understand your question. Do you mean a forEach loop? files.forEach(...) ?
|
2

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

Comments

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).

3 Comments

This won't work. Variables have function scope, not block scope.
There is an issue the no of variables in the function could not be increased. So there should be some other way out.
@Robert: This is wrong. You need to pass x to fn as a parameter. That's the only mechanism in javascript for breaking closures.
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));
}

8 Comments

This won't work. Variables have function scope, not block scope.
There's plenty of functions in there. He is still using x though which is just a string key.
Will it not effect the other callbacks in the iteration?
@AlanFoster: Any way to fix it, in your opinion?
@Esailija: Your code is buggy. It should be (function(x){.... Of course, to avoid confusion you really should use another letter.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.