Using setTimeout() with a for loop

I’m volunteering with a group running a coding bootcamp at the moment, and was looking through some of the sample code challenges that have been put together for helping the students learn to debug JavaScript. These challenges are quite simple at the moment, as they’ve only just covered the basics (variables, expressions, loops, functions etc.). As there are no model answers at the moment, I was looking at one of the questions with a view to providing one. The question runs as follows:

Find bug below and make sure it is working as expected.

Two way to handle this problem;
1- new ES6 type
2- Change logic inside loop

Desired output;
The index of this number is: 0
The index of this number is: 1
The index of this number is: 2
The index of this number is: 3
The index of this number is: 4
*/

const arr = [10, 12, 15, 21, 34];
for (var i = 0; i < arr.length; i++) {
  setTimeout(function() {
    console.log('The index of this number is: ' + i);
  }, 1000);
}

On the face of it, valid code, though when run, it outputs

// The index of this number is: 5

demonstrating that the loop and the timeout are running independently, and that by the time the first (and subsequent) setTimeout() function has run its code, the loop has already completed, leaving i set at 5.

Looking round to see what solutions are out there, it seems there are any number of solutions proffered, but none that I found that operate quite as I’d anticipated. I’m looking for something that operates as follows:

  1. Trigger routine
  2. 1 sec pause
  3. First console.log() appears
  4. 1 sec pause
  5. Second console.log() appears
  6. Etc.

Typical solutions I found were:

###Pause 1 sec, then display all

const arr = [10, 12, 15, 21, 34];

for(var i = 0; i < arr.length; i++) {
    (function() {
        var num = i;
        setTimeout(function() {
            console.log("The index of this number is: " + num);
        }, 1000);
    })();
}

Meets criteria, but relies on extending the setTimeout() duration

const arr = [10, 12, 15, 21, 34];

for (let i=0; i<arr.length; i++) {
 	setTimeout(function(){
    let temp = i;
		console.log("The index of this number is: " + temp); 
	}, 1000 * (i+1));
}

Note: This may be the model answer, but feels awkward

###Doesn’t pause before the first console.log()

const arr = [10, 12, 15, 21, 34];

for (var i = 0; i < arr.length; i++) {

  (function(i){

    window.setTimeout(function(){
      console.log("The index of this number is: " + i); 
    }, i * 1000);

  }(i));
}

Uses a closure, so may be too complex for the students at this stage of their learning

###Works as per the criteria, but abandons the use of a for() loop

const arr = [10, 12, 15, 21, 34];

function start(counter){
  if(counter < arr.length){
    setTimeout(function(){
      console.log("The index of this number is: " + counter);
      counter++;
      start(counter);
    }, 1000);
  }
}
start(0);

I actually rather like this one

###Doesn’t pause before the first console.log(), and adds complexity

const arr = [10, 12, 15, 21, 34];

function displayNumber(i) {
  setTimeout(function() {
    console.log(`The index of this number (${arr[i]}) is: ` + i);
  }, 1000 * i);
}

function callDisplay() {
  for( var i = 0; i < arr.length; i++ ) {
    displayNumber(i);
  }
}

callDisplay();

Again, not one I’d anticipate the students coming up with on their own.

So I’m wondering whether I’m missing something obvious. I’ve not found anything that will pause the loop whilst the setTimeout() does its thing, though I’ve seen several comments to say you can’t do that. Any thoughts out there on alternatives?

Here’s the original code:

const arr = [10, 12, 15, 21, 34];
for (var i = 0; i < arr.length; i++) {
  setTimeout(function() {
    console.log('The index of this number is: ' + i);
  }, 1000);
}

The problem is that by the time the timeout function runs, the loop has already completed.

The standard solution to this is to pass the index number as a function parameter, so that the function refers to that parameter instead of the loop index.

function showNumber(i) {
    return function () {
        console.log('The index of this number is: ' + i);
    };
}
const arr = [10, 12, 15, 21, 34];
for (var i = 0; i < arr.length; i++) {
  setTimeout(showNumber(i), 1000);
}
2 Likes

I don’t recall seeing one quite like that. It runs the same way as the first potential solution above i.e. pause for a second, then display all results. At the moment, I’m not sure that’s what the expected behaviour is, though I’d concede that the question could be clarified in that regard.

I think the most straight forward solution is to pass the additional parameters to .setTimeout() directly, which also saves you the wrapper function to create a closure:

const arr = [10, 12, 15, 21, 34]

for (let i = 0; i < arr.length; i++) {
  window.setTimeout(
    console.log, 
    1000 * (i + 1), 
    'The index of this number is: ' + i
  )
}

Personally I prefer a recursive calls for things like that though, which only schedules the next timeout after the previous has been executed (which also allows for easier cancellation). You can see a couple of possible solutions in this recent comment, although some of them are a bit tongue-in-cheek and not really meant to be used IRL. :-)

3 Likes

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.