I have referred to almost all the similar questions, youtube videos but nothing seems to work the way I need. I'll keep it very simple. 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? Please help.. It has been a couple of days of going through most of the similar questions but not achieving the expected result.
CodePudding user response:
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:
- Create a
Promise
which will actually run your asynchronoussetTimeout
code. - Use
async await
. Think ofawait
keyword as something that stops execution unless the promise is resolved. - 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. - 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);
}
})();
CodePudding user response:
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);
});
CodePudding user response:
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);
}
CodePudding user response:
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)