Misha Shblin
Misha Shblin

Reputation: 85

Better understanding of JavaScript closure inside loops

I'm dealing with the closure issue right now and I need a little help on the topic: Using this code for my training:

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

This solution timeouts for a second and then prints 1-10 in ascending order (as expected). Now, I want it to print j once every second:

1 (second passed...)
2 (second passed...)
3 (second passed...)
etc..

How can I make this happen without changing the formation of the loop?

Thanks in advance

Upvotes: 1

Views: 80

Answers (3)

Sacho
Sacho

Reputation: 2179

Alongside RGraham's answer, I'd like to point out that when learning javascript, you should try to avoid using function expressions - they make code confusing to read.

Here's what your initial code looks like, without a function expression:

for (var i = 1; i <= 10; i++) {
    setTimeout(doSomething(i), 1000);
}
function doSomething(i) {
    console.log(i);
}

Or an even clearer representation:

for (var i = 1; i <= 10; i++) {
    var k = doSomething(i);
    // doSomething doesn't explicitly return anything, so k === undefined
    setTimeout(k, 1000);
}
function doSomething(i) {
    console.log(i);
}

Also, as I mentioned in my comment, your problem isn't really related to your closure attempt, but rather the way asynchronous operations work in Javascript. The most commonly used ways to sequence asynchronous operations are the continuation passing style/callbacks and promises.

Upvotes: 1

Misha Shblin
Misha Shblin

Reputation: 85

Thanks RGraham for the solution:

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

Upvotes: 1

CodingIntrigue
CodingIntrigue

Reputation: 78535

setTimeout expects a function to be able to execute. You need to return a function and also, seeing as your setTimeout calls will occur one after another, you'll need to delay them

for (var i = 1; i <= 10; i++) {
    setTimeout(function(j) {
        return function() {
            console.log(j);
        }
    }(i), 1000 * i); // Delay
}

jsFiddle

That's without changing the formation of the loop. Although your best solution is to not use a for loop, but chain setTimeout calls instead:

var i = 0;
function start() {
    setTimeout(function() {
        i++;
        console.log(i);
        // If we haven't reached our limit, start again
        if(i < 10) start();
    }, 1000);
}
start();

jsFiddle

Upvotes: 3

Related Questions