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 asynchronous setTimeout
code.
- Use
async await
. Think of await
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);
}
})();