6

I want to pass variable setTimeoutfunction and do something with that. When I alert value of i it shows me numbers that i did not expected. What i m doing wrong? I want log values from 1 till 8.

var end=8;
for (var i = 1; i < end; i ++) {
       setTimeout(function (i) {
           console.log(i);   

       }, 800);
   }
Manse
  • 37,765
  • 10
  • 83
  • 108
alexeyb
  • 153
  • 2
  • 10
  • It is correct? Are you getting any errors? – Starx Apr 18 '12 at 10:33
  • 1
    Your problem is a scope problem: the `for` counter variable is called `i`, but the parameter is called `i` as well. So any value the counter-i has is overwritten as soon as you are in the parameter-i scope. Renaming either the counter or the parameter should help. – Dominik Schreiber Apr 18 '12 at 10:34
  • @DominikSchreiber: It's *partially* a scope problem. But just removing or renaming the `i` argument won't solve it. – T.J. Crowder Apr 18 '12 at 10:42

4 Answers4

13

The standard way to solve this is to use a factory function:

var end=8;
for (var i = 1; i < end; i ++) {
       setTimeout(makeResponder(i), 800);
   }

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

Live example | source

There, we call makeResponder in the loop, and it returns a function that closes over the argument passed into it (index) rather than the i variable. (Which is important. If you just removed the i argument from your anonymous function, your code would partially work, but all of the functions would see the value of i as of when they ran, not when they were initially scheduled; in your example, they'd all see 8.)


Update From your comments below:

...will it be correct if i call it in that way setTimeout(makeResponder(i),i*800);?

Yes, if your goal is to have each call occur roughly 800ms later than the last one, that will work:

Live example | source

I tried setTimeout(makeResponder(i),setInterval(i));function setInterval(index) { console.log(index*800); return index*800; } but it's not work properly

You don't use setInterval that way, and probably don't want to use it for this at all.


Further update: You've said below:

I need first iteration print 8 delay 8 sec, second iteration print 7 delay 7 sec ........print 2 delay 2 sec ...print 0 delay 0 sec.

You just apply the principles above again, using a second timeout:

var end=8;
for (var i = 1; i < end; i ++) {
       setTimeout(makeResponder(i), i * 800);
   }

function makeResponder(index) {
    return function () {
        var thisStart = new Date();
        console.log("index = " + index + ", first function triggered");
        setTimeout(function() {
            console.log("index = " +
                        index +
                        ", second function triggered after a further " +
                        (new Date() - thisStart) +
                        "ms delay");
        }, index * 1000);
   };
}

Live example | source

I think you now have all the tools you need to take this forward.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Hi, thanks, will it be correct if i call it in that way setTimeout(makeResponder(i),i*800);? I mean this part i*800, I think it remain the same . – alexeyb Apr 18 '12 at 11:02
  • I tried setTimeout(makeResponder(i),setInterval(i));function setInterval(index) { console.log(index*800); return index*800; } but it's not work properly – alexeyb Apr 18 '12 at 11:14
  • @alexeyb: I've added to the answer to address your questions above. – T.J. Crowder Apr 18 '12 at 11:34
  • I got it but i need to set interval in the first iteration to 8 sec then to 7 sec .... 3 sec according to the value that i have in index => index*1000 , but it always 1 sec.How to change it? – alexeyb Apr 18 '12 at 11:45
  • @alexeyb: Have you looked at the second example? (http://jsbin.com/oxarik) If so and that's not what you're trying to do, I don't understand what you're trying to do. – T.J. Crowder Apr 18 '12 at 11:47
  • Yep i saw it interval between output is one sec in your example... I need first iteration print 8 delay 8 sec, second iteration print 7 delay 7 sec ........print 2 delay 2 sec ...print 0 delay 0 sec.. – alexeyb Apr 18 '12 at 11:54
  • @alexeyb: It's just a further application of `setTimeout`, surely? Updated again. I think you can take it from here. – T.J. Crowder Apr 18 '12 at 12:03
3

Your problem is that you are referring to the variable i some time later when your setTimeout() function fires and by then, the value of i has changed (it's gone to the end of the for loop. To keep each setTimeout with it's appropriate value of i, you have to capture that value i separately for each setTimeout() callback.

The previous answer using a factory function does that just fine, but I find self executing functions a little easier than factory functions to type and follow, but both can work because both capture the variables you want in a closure so you can reference their static value in the setTimeout callback.

Here's how a self executing function would work to solve this problem:

var end=8;
for (var i = 1; i < end; i ++) {
       (function (index) {
           setTimeout(function() {
               console.log(index);
           }, 800);
       })(i);
   }

To set the timeout delay in proportion to the value of i, you would do this:

var end=8;
for (var i = 1; i < end; i ++) {
    (function (index) {
        setTimeout(function() {
            console.log(index);
        }, index * 800);
    })(i);
}

The self executing function is passed the value of i and the argument inside that function that contains that value is named index so you can refer to index to use the appropriate value.


Using let in ES6

With the ES6 of Javascript (released in 2015), you can use let in your for loop and it will create a new, separate variable for each iteration of the for loop. This is a more "modern" way to solve a problem like this:

const end = 8;
for (let i = 1; i < end; i++) {            // use "let" in this line
     setTimeout(function() {
         console.log(i);
     }, 800);
 }
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • ok it work but if i want to set timeout acordingly ..How i shoul do that ? For example i want to put each number and delay number*800, if you simply add i*800 it delay 1 sec in every loop change. – alexeyb Apr 18 '12 at 11:25
  • @alexeyb - You use the argument named `index`. See the second example I added to my answer. – jfriend00 Apr 18 '12 at 16:47
  • Added ES6 example with `let` since that's the "modern" way to solve this type of issue. – jfriend00 Feb 20 '20 at 22:13
0

The main reason for this to not to work, is because, of the setTimeout which is set to run after 800 and the scope of i.

By the time it executes which the value of i will already have changed. Thus no definitive result could be received. Just like TJ said, the way to work this around is through a handler function.

function handler( var1) {
    return function() {
      console.log(var1);  
    }        
}

var end = 8;
for (var i = 1; i < end; i++) {     
   setTimeout(handler(i), 800);
}

Demo

Starx
  • 77,474
  • 47
  • 185
  • 261
-2

setTimeout accepts variables as additional arguments:

setTimeout(function(a, b, c) {
    console.log(a, b, c);
  }, 1000, 'a', 'b', 'c');

Source.

EDIT: In your example, the effective value of i will likely be 8, since the function is merely to be called after the loop has finished. You need to pass the current value of i for each call:

var end=8;
for (var i = 1; i < end; i ++) {
  setTimeout(function (i) {
      console.log(i);   
   }, 800, i);
}
Iso
  • 3,148
  • 23
  • 31
  • 1
    keep in mind though that "**Note that passing additional parameters to the function in the first syntax does not work in Internet Explorer.**" – Gabriele Petrioli Apr 18 '12 at 10:35
  • You could always use the old (but deprecated) syntax for IE: `setTimeout('callable(' + i + ')', 1000);`. – Iso Apr 18 '12 at 10:36
  • This doesn't even work in **IE9**, much less earlier versions. This answer might be useful sometime in 2018 or so. – T.J. Crowder Apr 18 '12 at 10:41