setTimeout() method inside a while loop [duplicate]
Asked Answered
C

8

22

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?

Cristie answered 9/6, 2016 at 14:2 Comment(2)
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.Whiteeye
setTimeout expects a Function as the first parameter but you're passing the result of myfunc03 (which is underfined because you're invoking it)Bunni
B
36

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>
Buckskins answered 9/6, 2016 at 14:5 Comment(2)
Thorough explanation. I was running into the same issue. Great examples, thanks!Steinbok
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!Loggerhead
D
15

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);
Delores answered 9/6, 2016 at 14:34 Comment(0)
D
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");
  }
})();
Disheveled answered 22/10, 2019 at 14:25 Comment(0)
F
3

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);
  }
})();
Fennel answered 6/8, 2022 at 19:48 Comment(0)
B
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>
Bluebottle answered 9/6, 2016 at 14:16 Comment(0)
E
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); 
    
Escritoire answered 9/6, 2016 at 14:13 Comment(2)
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 jsfiddle.net/ofpp26or/2 to jsfiddle.net/ofpp26orPentosan
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.Escritoire
R
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>
Robertson answered 27/3, 2021 at 16:21 Comment(0)
S
-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

Sinclare answered 9/6, 2016 at 14:6 Comment(6)
Please work on your answers. this is not readable nor understandableMayenne
@Mayenne yeah my bad!Sinclare
You did not address the closure issueMayenne
I know, others already did that and originally I had missed it. So I just modified my original answer. Didn't feel right to copy off someone else's answer :)Sinclare
But the result is a wrong answer that has 3 downvotesMayenne
@Mayenne it's okay. I don't write stuff just for votes or reputation. Just killing some free time and helping fellow coders out :) Anyways corrected the answer as you insistedSinclare

© 2022 - 2024 — McMap. All rights reserved.