0

I have two asynchronous functions the one nested in the other like this:

  //Async 1
  document.addEventListener("click", function(event){
     for (var i in event){
        //Async 2
        setTimeout(function(){
           console.log(i);
        }, 200*i);
     }
  });

What I want is to be able and print each entry(i) of the event object. The output on Firefox is however this:

MOZ_SOURCE_KEYBOARD
MOZ_SOURCE_KEYBOARD
MOZ_SOURCE_KEYBOARD
MOZ_SOURCE_KEYBOARD
..

If I move console.log(i) outside Async 2 then I get the correct result:

type
target
currentTarget
eventPhase
bubbles
cancelable
..

Why doesn't it work correctly when reading the i inside async 2? Shouldn't event be "alive" inside the whole Async 2 block of code as well?

Pithikos
  • 18,827
  • 15
  • 113
  • 136

3 Answers3

1

Use this:

document.addEventListener("click", function(event){
    var count = 1;
    for (var i in event){
        //Async 2
        setTimeout((function(i){
            return function () {
                console.log(i);
            };
        })(i), 200 * count++);
    }
});

DEMO: http://jsfiddle.net/AQykp/

I'm not exactly sure what you were going for with 200*i, since i is a string (not even digits). But I tried to fix it with counter in my answer, assuming what you really wanted.

So the reason you were seeing the results you were was because of the common closure problem. The setTimeout callback will execute after the for loop completes, leaving i as the last key in event. In order to overcome that, you have to add a closure that captures the value of i at that point in the loop.

Ian
  • 50,146
  • 13
  • 101
  • 111
  • The actual problem has to do with google maps so I tryied to simplify the problem and by accident "200*i" slipped through. Is there some resource that explains this any further? – Pithikos Apr 13 '13 at 00:59
  • Maybe this helps - http://stackoverflow.com/questions/1451009/javascript-infamous-loop-problem . The point is that the `setTimeout` executes asynchronously, later. By the time it executes, the synchronous `for` loop has already completed. Since the `setTimeout` references `i`, it is the last item in `event`. In order to capture it, you have to use a closure. Just Google "Javascript closure" to learn more about them, if the link I provided isn't enough – Ian Apr 13 '13 at 01:11
1

setTimeout uses i as it appears in async1. That is, it references i instead of using the value of i when the timeout function is created. When your timeout function finally runs, it looks at the current value of i, which is the last key in event after the for-loop.

You can avoid this by using a self-calling function, such that the arguments of that function are local to the timeout function:

for (var i in event) {
    setTimeout((function(i) {
        return function() { console.log(i); }
    })(i), 200 * i);
}
Bart
  • 26,399
  • 1
  • 23
  • 24
0

Event is indeed alive inside your callback. The problem is that, by the time your function is executed, the value of i has changed (from what I can see from the output, the loop has ended and i has reached its maximum value) and thus outputting the same value for every callback.

If you use the function Ian commented, you will curry the actual value into a new function. That way the value of i can vary, you captured the current value inside the inner function

Kenneth
  • 28,294
  • 6
  • 61
  • 84