Alex_B
Alex_B

Reputation: 1661

Unexpected behavior: Javascript, setTimeout(), and IIFE

Javascript, Event loop, setTimeout, IIFE, closure

Based on references below, my understanding of the following code is:

setTimeout() is non-blocking and handled by the Browser Web APIs, which put the callbacks on the callback queue when the timer is done. Then the event loop waits for the call stack to be free to run each callback in turn. setTimeout closure closes over the anonymous IIFE and has the correct value of index for each iteration.

for(var i = 0; i < 3; i++){
    (function(index){
        setTimeout(function(){
            console.log(index);
        }, 5000);
    })(i);
    console.log("loop="+i);
}
/*Output in console is
loop=0
loop=1
loop=2
//after 5 seconds
0
1
2
*/

I'm looking for an explanation of what's happening with the following code in Chrome.

for (var i = 0; i < 3; i++) {
    setTimeout(
        function(index) { 
            console.log(index);
        }(i), 5000
    );
    console.log("loop="+i);
}
/* Output in console without any delay is:
0
loop=0
1
loop=1
2
loop=2
*/

Why is 'console.log(index)' executed immediately, without a 5 second delay?

How does the web API execute setTimeout() with a callback as an IIFE?

Are any callbacks put in the callback queue?

Does the event loop move any callbacks to the call stack?

Or is setTimeout() being ignored and its callback being executed immediately on the call stack?


References I've consulted:

Philip Roberts: What the heck is the event loop anyway? | JSConf EU 2014 https://www.youtube.com/watch?v=8aGhZQkoFbQ

Philip Roberts Help I'm stuck in an event loop 2016 https://www.youtube.com/watch?v=6MXRNXXgP_0

Call Stack & Event Loop https://www.youtube.com/watch?v=mk0lu9MKBto

JavaScript closure inside loops – simple practical example

Use IIFE in setTimeout in a loop, but why?

Upvotes: 2

Views: 470

Answers (3)

prasad_
prasad_

Reputation: 14287

I'm looking for an explanation of what's happening with the following code in Chrome.

for (var i = 0; i < 3; i++) {
    setTimeout(
        function(index) { 
            console.log(index);
        }(i), 5000
    );
    console.log("loop="+i);
}
/* Output in console without any delay is:
0
loop=0
1
loop=1
2
loop=2
*/

Consider the following statement:

function(index) { 
    console.log(index);
}(i)

This is an anonymous function and executed immediately (the parentheses '()' at the end executes the function): see the syntax function(param) {...}(). So the effect is that for each iteration the above code is executed immediately.

The result is (as you see it):

0
1
2

The setTimeout at MDN. method expects a function (to be executed after the timer expires) or code as its first parameter. In this case you have code (not a function) that executes immediately. So, you see the result immediately.

The delay of 5 seconds has no effect, its never used. There is nothing to execute after the delay.

The effect is same in Firefox too.

You can try the code without the parentheses at the end of the anonymous function and see what happens:

function(index) { 
    console.log(index);
}

The function will execute after the delay of five seconds, in this case!

Upvotes: 0

Uma
Uma

Reputation: 846

In second example you are not passing the function to the setTimeout but you rather passing it's result of the function call (in this case it's void).

        function(index) { 
            console.log(index);
        }(i)

you see, in this example your function invokes immediately thus, there's nothing to call later, and console logs in order.

Upvotes: 0

CertainPerformance
CertainPerformance

Reputation: 370699

In

setTimeout(
    function(index) { 
        console.log(index);
    }(i), 5000
);

You're invoking the first argument passed to setTimeout immediately. When the interpreter comes across the setTimeout line, it first tries to resolve all of its arguments to values. The first argument is a function invocation, so it invokes that function in the expectation that it will resolve to another function - just like how one could do

setTimeout(makeFn('foo'), 5000);

where makeFn returns a function.

So, in your code, the

    function(index) { 
        console.log(index);
    }(i)

runs immediately, but it doesn't return anything - the interpreter resolves the setTimeout line to

setTimeout(undefined, 5000);

but undefined isn't a function, so nothing asynchronous gets queued up.

You don't have any IIFEs here - put the whole setTimeout line in an IIFE instead:

for (var i = 0; i < 3; i++) {
  ((i) => {
    setTimeout(
      function() {
        console.log(i);
      }, 500
    );
    console.log("loop=" + i);
  })(i);
}

(or, of course, use const or let instead of var - best to avoid var, its hoisting and function scope is very unintuitive and requires verbose workarounds like these in for loops)

for (let i = 0; i < 3; i++) {
  setTimeout(
    function() {
      console.log(i);
    }, 500
  );
  console.log("loop=" + i);
}

Upvotes: 3

Related Questions