You-dont-know-js: Problem with closures.

Created on 18 Mar 2016  路  10Comments  路  Source: getify/You-Dont-Know-JS

for (var i=1; i<=5; i++) {
    (function(){
        setTimeout( function timer(){
            console.log( i );
        }, i*1000 );
    })();
}

I am confused!

Why this won't work?

I think that engine goes line by line and executes the code line by line after compiling it and in this case the engine will go into the loop then execute all the code in which case the output must be like :-

0
1
2
3
4

Why it is not waiting for 1000 ms? How for loop is iterating ahead while the function setTimeout is not yet executed?

Most helpful comment

I obviously didn't really knew what I was talking about, so I took the time to properly learn it.

Here it is JS Slo-Mo: for loop, closure, async callbacks.

Thanks @getify and @oldergod !

All 10 comments

The for loop is executing the IIFE five times.

In it, setTimeout is building a future stack of timer functions.

Deeper in it, each timer function closes over the i variable, by reference.

The for loop takes less then a second to execute. Hence, every timer functions execution from the future stack occurs after the loop has finished, each referencing the post-for i value: 6.

At the end of the for loop, referencing i is equal with getting the number 6 value. That's why i must be passed by value to future timer functions, during the loop.

Deeper in it, each timer function closes over the i variable by reference

Nope. :) This is a classic case of getting the right answer with the wrong reason. Reference vs value doesn't come into play with closure. Orthogonal. Moreover, a simple value primitive like a number would always be by-value.

The reason the IIFE as shown doesn't fix anything is because there's still only one i being closed over. If the IIFE has its own i then each timeout callback closes over a diff i thereby preserving the value in each iteration. :)

In the next example, the working example, you pass i to the IIFE. i being a primitive value, you pass it by value. The parameter-value j is then bounded to each IIFE scope, indiferent to i values changing the next loop steps over, and is being closed over by a timer function in each IIFE. By reference. Or not? :) Thanks for taking the time! By value, you are right.

for (var i=1; i<=5; i++) {
    (function(j){
        setTimeout( function timer(){
            console.log( j );
        }, j*1000 );
    })( i );
}

In the end, the five IIFE stack produces another five timer function stack. What's different is the inner scope of each IIFE, accessible to their own respective inner timer functions. In the first example, the inner scope of each IIFE holds public i, with the value of 6. In the second one, the scope of each IIFE holds private and different j values, thus allowing their respective timer functions to close over different values.

To summarize, we are building different and private IIFE scopes, for each inner timer function to close over, by passing the i value as the j parameter, different and private to each outer holding IIFE. If not, by the time they execute, each timer function in the future stack closes over the same public outer scope, where the for loop has finished execution, leaving i to holding the value 6. I hope I got it right this time? :)

Got it thanks @getify and @itmitica. I was also thinking like @itmitica , that all i's are referencing to the same variable so 6 is printed out 5 times. Closures go against my intuition. :D

The for loop takes less then a second to execute. Hence, every timer functions execution from the future stack occurs after the loop has finished.

I believe this to be wrong as the timed code will not be executed before the loop is finished, even if the loop takes more than a second because the stack will be stuck at the function/script containing the loop.

the stack will be stuck at the function/script containing the loop.

@oldergod could you please go into the details of this, I am not able to get it.

See: https://youtu.be/8aGhZQkoFbQ?t=771

After one second, the browser will indeed add the timed code to the task queue but then, this new task can be executed only when the stack gets empty. The stack can be empty only if the loop has finished .

No matter how long a piece of code works, it won't be "interrupted" by a shorter timeout being ready to fire. The event loop is where things are scheduled asynchronously and every item gets in line behind everything else waiting. Timeouts will always happen at a minimum on the next event loop tick, never the current one.

Thanks now I finally a hold on this thing!

I have not gone through the Async part of the book series so I didn't know the details of it. But now I got it.

I obviously didn't really knew what I was talking about, so I took the time to properly learn it.

Here it is JS Slo-Mo: for loop, closure, async callbacks.

Thanks @getify and @oldergod !

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Beaglefoot picture Beaglefoot  路  3Comments

williamvittso picture williamvittso  路  3Comments

madmadi picture madmadi  路  5Comments

aszx87410 picture aszx87410  路  3Comments

laoshaw picture laoshaw  路  3Comments