Closure – II

Read the first part here

Let us see another famous and good example for understanding the concept of closure. A prior knowledge of how callbacks and event loop work in JavaScript is required.

var alphabets = ["A", "B", "C", "D", "E", "F"];
for (var i = 0; i < 5; i++) {
    setTimeout (function () {
        console.log (i, alphabets[i]);
    }, 0);
}

// Q. What is the output of this code?

setTimeout is not a native function in JavaScript. It is an API similar to XMLHttpRequest which enables us to extend JavaScript to asynchronous in nature. setTimeout is a function which accepts two arguments, a callback function to be executed and, delay in milliseconds. However, the delay specified is not exact, i.e. it is not guaranteed that after the delay the callback function will be executed. The delay is the minimum amount of time after which the callbacks will be invoked. The execution of callback function is dependent on the event queue and only when the call stack is empty, such callbacks [waiting to be executed] will be executed.

for loop in JavaScript is a blocking code, i.e. unless the for loop does not reach the break condition, the JavaScript engine will not move to the next block of the code. In the above example, inside for loop we have added the setTimeout with a callback function to print the value of alphabets array for the given index with a delay of 0 milliseconds. As discussed earlier, the callback will only execute once the call stack is empty. And when the call stack becomes empty and, there are 5 callbacks waiting to be executed.

The index of the array starts from 0 and the break condition of the for loop will force the for loop to exit when the value of i becomes 5 as the breaking condition is, i should be less than 5.

Let’s come back to the example we were discussing. When the call stack is empty and we have 5 callback functions waiting to be executed. And here is the output of the above example:

// following line will be printed 5 times
5 "F"

Did you expect this?

When the callbacks were in execution, the value of i had become 5. In other words, when the value of i became 5, the for loop exited and callbacks started to execute. And that’s why we see this output, i = 5 and alphabets[5] = F.

So how do we print numbers from 0 to 4 and alphabets from A through E. But another question is, why would we need to write such code. What needs to be achieved here by adding a delay using setTimeout. Since for loop is a blocking code and let’s say we need to run the loop for a higher value of breaking condition and the computation inside the loop is also heavy. In this case, the JavaScript will be stopped at that point for a that much time, and if there were any DOM manipulation in progress and dependent on this computation, the rendering will be blocked.

In such cases, in order to unblock the execution, we can use this technique of adding the setTimeout inside for loop to unblock the code. Which means, the heavy computations will be sent to the wait queue and it’ll run when the JavaScript has finished its other tasks [perhaps showing a loaded in this case would be a good idea]. But, we need to have the correct index of the loop inside the setTimeout callback. And that’s why we need the closure. We will see how, in a bit.

If you read carefully, you would find that this is similar to doing an Ajax [XMLHttpRequest] call. The time taken by heavy computation could be replaced by the time taken by the server to respond.

Before I solve this issue of sending the right index to the callbacks, I would want you to do a small experiment. Try to add a delay of 1000 milliseconds (1 seconds) in the setTimeout and see what happens. You would find that, the output will appear on the screen after 1 second. All of them, together. Not each functions will get their own delay. Similarly, when we keep the delay of 1 second, and let’s say the other parts of code were in execution, the callbacks will stay waiting, even after a second. That’s why we say, the delay mentioned is not a guaranteed one, it’s the minimum amount of delay after which callbacks will be executed.

Let’s come back to the problem that we had. As discussed earlier, we need to create functions to create scope. Here is what we need to do:

  1. Create a new scope.
  2. Pass the value of index in new scope.
  3. It will be treated as a local variable in the new scope. Its value will be preserved when the callback will be executed as we know closures can preserver the scope and its data.
var alphabets = ["A", "B", "C", "D", "E", "F"];
for (var i = 0; i < 5; i++) {
    // create a function to create a new scope.
    // invoke this function to execute the code inside it and
    // also pass the value of i to this new function/scope.
    // this is also call immediately invoked function expression.
    (function (j) {
        setTimeout (function () {
            console.log (j, alphabets[j]); // j is local inside IIFE
        }, 0);
    }) (i);
}