20

I have read the relevant pages on w3schools and other similar questions here but cannot seem to understand what's wrong about the following bit :

var myfunc03 = function (i) {
  document.getElementById('d01').innerHTML += 100-i+"<br>";
};

var myFunc01 = function() {
  i=0;
  while (i<100) {
    setTimeout(myfunc03(i), 1000)
    i++;
  }
};

when myFunc01(); is run.

There's no pause whatsoever and all possible values for i is listed at once.

Is there a logical mistake here?

havz
  • 303
  • 1
  • 2
  • 5
  • That of course highlights his second mistake -- accessing a modified variable (`i`) from within a closure. When the timeout fires, `i` will have changed to whatever the last one is. – Dawson Toth Jun 09 '16 at 14:04
  • 1
    `setTimeout` expects a `Function` as the first parameter but you're passing the *result* of `myfunc03` (which is `underfined` because you're invoking it) – haim770 Jun 09 '16 at 14:06

8 Answers8

35

The while loop will not wait for setTimeout() to complete. You need to set different time delay for each to execute them with different times and use closure for holding the value of i. Also in your case, function will be executed initially and return value is setting as argument in setTimeout(), so either you need to call the function inside an anonymous function or set the function directly.

var myFunc01 = function() {
  var i = 0;
  while (i < 100) {
    (function(i) {
      setTimeout(function() {
        document.getElementById('d01').innerHTML += 100 - i + "<br>";
      }, 1000 * i)
    })(i++)
  }
};

myFunc01();
<span id="d01"></span>


Although setInterval() can be used here

var myFunc01 = function() {
  var i = 0;
  // store the interval id to clear in future
  var intr = setInterval(function() {
    document.getElementById('d01').innerHTML += 100 - i + "<br>";
    // clear the interval if `i` reached 100
    if (++i == 100) clearInterval(intr);
  }, 1000)

}

myFunc01();
<span id="d01"></span>
ballade4op52
  • 2,142
  • 5
  • 27
  • 42
Pranav C Balan
  • 113,687
  • 23
  • 165
  • 188
  • 2
    Thorough explanation. I was running into the same issue. Great examples, thanks! – vapcguy Aug 12 '19 at 20:40
  • First workaround not working for me. All actions inside setTimeout executing simultaneously. All like in this article - coderwall.com/p/_ppzrw/be-careful-with-settimeout-in-loops Looks like it works inside "for" but not "while" ( Second works fine! – Vasin Yuriy Aug 18 '23 at 14:31
14

You can do it more simply with recursion:

var i = 0;
function f1() { ... };   
function f() {
    f1();
    i += 1;
    setTimeout(function() {
        if(i < 100) {
            f();
        }
    }, 1000);
}
f();

Example

var i = 0;

var myfunc03 = function(i) {
  document.getElementById('d01').innerHTML += 100 - i + "<br>";
};

var myFunc01 = function() {
  myfunc03(i);
  i += 1;
  setTimeout(function() {
    if (i < 100) {
      myFunc01();
    }
  }, 1000);
}

myFunc01();
<div id="d01"></div>

A reusable function

function say(sentence) {
  console.log(sentence);
}

function sayHello() {
  say("Hello!");
}

var fn = sayHello;
var count = 10;
var ms = 1000;

function repeat(fn, count, ms) {
  var i = 0;

  function f() {
    fn();
    i += 1;
    setTimeout(function() {
      if (i < count) {
        f();
      }
    }, ms);
  }

  f();
}

repeat(fn, count, ms);
Community
  • 1
  • 1
4

while waiting for setTimeout :

(async () => {
  var i = 0;
  while (await new Promise(resolve => setTimeout(() => resolve(i++), 1000)) < 100) {
    console.log("I get printed 100 times every second");
  }
})();
shoesel
  • 1,198
  • 8
  • 15
2

You can create Timeout function and use it with async/await

// Timeout function
const timeout = (ms) => new Promise(resolve => setTimeout(resolve, ms));

// Run some loop in async function
(async () => {
  // Loop for 5 times
  for(let i = 0; i <= 5; i++) {
    // Do some stuff
    console.log(`Some stuff ${i}`);
    // Wait for timeout 1000 ms
    await timeout(1000);
  }
})();
tarkh
  • 2,424
  • 1
  • 9
  • 12
1

Yes. There are 2 problems in your code:

  1. The setTimeout function accept a function as the first argument, but in your code, myfunc03(i) returns nothing
  2. The while loop won't meet you needs, instead, you have to use recursive function. Since the second function should be invoked after the first timeout is fired.

Sample code:

var myfunc03 = function (i) {
  setTimeout(function() {
    document.getElementById('d01').innerHTML += 100-i+"<br>";
    if (i < 100) {
      i++;
      myfunc03(i);
    }
  }, 1000);
};

var myFunc01 = function() {
  myfunc03(0);
};

myFunc01();
<div id="d01"></div>
hoozecn
  • 497
  • 4
  • 14
0
  • I think you are missing a semicolon on the setTimeout and you should try passing the arguments in the below fashion:

    setTimeout(myfunc03, 1000*i, i); 
    
Utkarsh Sinha
  • 941
  • 2
  • 11
  • 30
  • For modern browsers, this is the best solution, because it avoids the awkwardness of closures. However, the second parameter should be `1000 * i`; otherwise, you're setting 100 timeouts at the same time to 1s. Compare https://jsfiddle.net/ofpp26or/2/ to https://jsfiddle.net/ofpp26or/ – Rick Hitchcock Jun 09 '16 at 14:19
  • Yea, that's correct. I intended to only show syntax-wise, as that might have caused js-errors. He might use time-parameters as per his logic requirements. – Utkarsh Sinha Jun 09 '16 at 20:35
0

One simple option is use setInterval() method instead. The setInterval will execute the code everytime as a loop until you break it with the clearInterval() method.

Example:

var index = 0;
let incrementEveryHalfSecond = setInterval(function(){
  index++;
  document.querySelector("body").innerHTML += index+'</br>';
  if(index == 10) clearInterval(incrementEveryHalfSecond)
}, 500) 
<body></body>

Observe that you decide break of loop, passing the variable that is store the setInterval method as parameter in the clearInterval method. Now you can create functions to get a better clear code. w3schools documentation

Question answer:[from 100 to 1] I inverted the order of i to simplify, but you can also do with "100-i".

var myfunc03 = function (i) {
  document.getElementById('d01').innerHTML += i+"<br>";
};

var myFunc01 = function() {
  let i=100;
  let incrementEveryOneSecond = setInterval(function(){
    myfunc03(i);
    if(--i == 0) clearInterval(incrementEveryOneSecond);
  }, 1000) 
 
};
myFunc01();
 <span id="d01"></span>
-1

the while method runs quickly and all timeout almost gets executed after first second.. what you can do is

  1. Instead of while call $timeout from myfunc03 for next value
  2. inside your while call timeout with increasing seconds like i*1000

Also, as others pointed out you can't call functions with params like that from setTimeout use anonymous function like

...
while (i<100) {
  setTimeout(
     function(i){
        myfunc03(i);
     }, i*1000);
   i++;
}
...

for that

Viral Shah
  • 47
  • 5