0

I have referred to almost all the similar questions, YouTube videos but nothing seems to work the way I need. Here is the code:

var arr = [1,2,3,4]
for(var i = 0; i<arr.length;i++){
    console.log("Before Timeout" + i);
    setTimeout(function(){console.log("Array val of arr["+i+"]="+arr[i]);},5000);
    console.log("After Timeout" + i);
}

My requirement is that the code displays

Before Timeout0
**Waits for 5 seconds**
Array val of arr[0]=1
After Timeout0
Before Timeout1
**Waits for 5 seconds**
Array val of arr[1]=2
After Timeout1
And so on.

But when I run the code the output is

Before Timeout0
After Timeout0
Before Timeout1
After Timeout1
Before Timeout2
After Timeout2
Before Timeout3
After Timeout3
Array val = arr[4]undefined

Why the settimeout code is being skipped? What am I doing wrong or what am I misunderstanding here and How do I get the desired result?

halfer
  • 19,824
  • 17
  • 99
  • 186
Rishav1112
  • 23
  • 4
  • Every time you run the loop a Timeout Attached to the same cell in the array, goes to the "pool" and there it waits for 5 seconds before it is executed. At this time the rest of the code continues and the loop appends another Timeout the next cell. What actually happens is that you send several cells to wait with a Timeout and then they are executed one after the other quickly – adir Oct 08 '22 at 08:04
  • @adir Yes this is good piece of information. I did not know this was the functioning of setTimeout function. But shouldn't the execution stop at the setTimeout command? – Rishav1112 Oct 08 '22 at 09:52

4 Answers4

2

THE PROBLEMS (SKIP TO BOTTOM FOR SOLUTION):

What you are trying to do is not easy to achieve with the code already present.

Let us first see why this does not work:

var arr = [1,2,3,4]
for(var i = 0; i<arr.length;i++){
    console.log("Before Timeout" + i);
    setTimeout(function(){console.log("Array val of arr["+i+"]="+arr[i]);},5000);
    console.log("After Timeout" + i);
}

console.log(i);

setTimeout sets an async operation, which will run minimum after 5000 ms. The JS execution does not stop for that. Read up about event loop and asynchronous operations to get more clarity.

The for loop itself will run in synchronous manner and all your callbacks inside setTimeout will be scheduled (but will run in future). The rest of the flow continues.

That is why all the Before and After logs are run first. And later the timeout code runs. When the callback code runs (after ~5 seconds) the value of i is already 4. That is why you are accessing arr[i] you actually access arr[4] which is undefined.

This is how var variables work. There will be lots of answers on SO to help you with scoping. But the gist is that all the above i point to the same instance i. This i exists in the outer scope as shown in the log.

Replacing var with let will sort this part out. let is block scoped and only exist between {}. Every iteration will have a different instance of i.

var arr = [1,2,3,4]
    for(let i = 0; i<arr.length;i++){
        console.log("Before Timeout" + i);
        setTimeout(function(){console.log("Array val of arr["+i+"]="+arr[i]);},5000);
        console.log("After Timeout" + i);
    }

console.log(i);

Notice how it is not accessible outside the loop.

But there is still the problem that your execution order is not as expected.

THE SOLUTION:

To actually solve your problem, there might be multiple approaches. I went with this one:

  1. Create a Promise which will actually run your asynchronous setTimeout code.
  2. Use async await. Think of await keyword as something that stops execution unless the promise is resolved.
  3. Use an async immediately invoked function expression as await can not be used on top levels. Only inside functions which have async keyword in front of them.
  4. Keep the Before log outside the promise so it runs first and then later the other two logs run.

const prom = (func) => new Promise((resolve) => {
setTimeout(() => {
  func();
  resolve();
},5000);
});

(async () => 
{
var arr = [1,2,3,4]
for(var i = 0; i<arr.length;i++){
   console.log("Before Timeout " + i);
   let func = () => {
   console.log("Array val of arr["+i+"]="+arr[i]);
   console.log("After Timeout " + i);
   }
    await prom(func);
}
})();
Tushar Shahi
  • 16,452
  • 1
  • 18
  • 39
  • 1
    Thank you so much for such a detailed explanation. I am able to get the required code running by using your code as a template. Understood a lot about setTimeout function. Been struggling with this since more than a couple of days. Thanks again. – Rishav1112 Oct 08 '22 at 13:26
  • 1
    And the array value issue too.. – Rishav1112 Oct 08 '22 at 13:27
0

Because i equals 4 when the code inside the setTimeout is being executed and there is no element in the array at this index.

I would recommend forEach instead:

const arr = [1, 2, 3, 4]
arr.forEach((e, i) => {
    console.log("Before Timeout" + i);
    setTimeout(() => {
        console.log("Array val of arr[" + i + "]=" + e);
    }, 5000);
    console.log("After Timeout" + i);
});
Optimix
  • 66
  • 4
0

Actually to get the output you want we have to pause the execution of the for loop, which is not possible, but you can set up timers and cause code to be executed at a later point with the setTimeout() and setInterval().

Here what's happening is the setTimeout() is making the function run after 5sec but by the time the function starts running the for loop finishes running and the value of i reaches the end and that's the reason why you are getting Array val = arr[4] output 4 times. You can just pass the value of i to solve this. You can use the below code to get the same structure you wanted but not the way:

var arr = [1,2,3,4]
for(var i = 0; i<arr.length;i++){
    task(i);
}
function task(i){
    setTimeout(function(){
        console.log("Before Timeout" + i);
        console.log("Array val of arr["+i+"]="+arr[i]);
        console.log("After Timeout" + i);
    }, 5000);
}
  • Yes, with the above code I have the output in the required format but it still doesn't pause for 5 seconds before every output. How do I achieve that? – Rishav1112 Oct 08 '22 at 10:01
  • @Rishav1112 I have been searching for a while and found some useful ones. This is a similar one, check this[link](https://stackoverflow.com/questions/3583724/how-do-i-add-a-delay-in-a-javascript-loop) – Aswin Shailajan Oct 08 '22 at 11:58
0

You need to call the next setTimeout from within setTimeout so that you get the pause between each console.log

var arr = [1,2,3,4]

function printItemAfterPause(index) {
    
    setTimeout( function() {
        
        console.log(`Item at pos ${index}: ${arr[index]}`)
        index ++;
        if(index < arr.length){
            printItemAfterPause(index)
        }
    }, 1000)
    
}

printItemAfterPause(0)
Dave Pile
  • 5,559
  • 3
  • 34
  • 49