Three solutions to the loop problem

The lack of a block scope in JavaScript leads to what is often called The Infamous Loop Problem, where we try to use the (supposed) cycle variable in a for loop to set a callback function.

Conceptually, the solution is quite easy. The trouble is caused by the loop variable, that actually does not have a local scope, so we provide to it a local scope. In JavaScript we have only function scope, so a function would help us to find a way out.

The question is, which function? Whichever it is better for you in your context, I'd say. Let's see the three answers that I guess are more natural.

In the previous post I have written a buggy piece of code that was storing a few functions when looping on an array, an then, in another for loop, it called those functions. Now I modify it to let it work as I expected, following three different approaches.

Closure in loop

This is the most idiomatic solution using "pure" JavaScript. We wrap the code in the for loop around a closure, designed just to provide a local scope for the loop variable.
function doSomething(msg) {
    console.log(msg);
}

var messages = [ "hello", 42, { pi: 3.14 } ];
var buffer = [];

for(var i = 0; i < messages.length; ++i) {
    buffer[i] = (function(j) { // 1
        return function() { // 2
            doSomething(messages[j]);
        }
    })(i); // 3
}

// ...

for(var k = 0; k < buffer.length; ++k) {
    buffer[k]();
}
1. We put in buffer the function returned by a closure, here defined, that take as input a parameter, named j, that is assigned a value defined in (3).
2. The function returned by the closure is almost the same function we defined in the original code. The difference is that I don't use anymore the loop variable, but the closure parameter.
3. This is the core of the solution. I assigned to the closure parameter the loop variable, in this way the closure captures its current value, that now has a local scope, as required.

After a while, I guess this would look as the most natural approach, but if your JavaScript mileage is low, you could be find it a bit overwhelming. In this case, maybe you could be more at ease with the following variation.

Closure in the function

The idea is to move the closure from the for loop to the recipient function. I refactor the code above, rewriting the for loop and doSomething(), renamed doSomethingElse() for the sake of clarity.
function doSomethingElse(index) {
    return function() { // 1
        console.log(messages[index]);
    }
}

// ...

for(var i = 0; i < messages.length; ++i) {
    buffer[i] = doSomethingElse(i); // 2
}

// ...
1. The closure has been moved here. Notice that this function does not run any code, but returns a function.
2. Just a simple assignment!

This code is so much readable, but I am losing the connection between the loop (2) and the reason why I need a closure in (1). Someone who reads just doSomethingElse() would probably wonder why it returns a closure instead of doing directly its job.

jQuery.each()

If we use the jQuery library, we can get the advantages of both the previous solutions using the each() method.

We use the original doSomething() function, and we don't need a closure in the for loop, jQuery.each() is a function, so it provides its own scope.
$.each(messages, function(index, value) { // 1
    buffer[index] = function() { // 2
        doSomething(value);
    }
});
1. For each element in the messages array, we call a function where the first parameter is the current index, and value the message element currently accessed.
2. There's no need of a closure, we simply assign to the buffer element a function that calls doSomething(). The capturing of local values is performed by jQuery.each().

No comments:

Post a Comment