Unable to figure out the output of simple javascript code - javascript

function func2() {
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 2000);
}
}
func2();
Due to closure, the console.log statement should remember its outer lexical environment, which is first setTimeout and then the for loop. Since i is not available in the setTimeout context, it looks in the execution context of the for loop, and searches for the value of i there.
Now, that value, after 2 seconds, should have been 3 for all three executions of console.log. But it displays the output as:
0
1
2

You have used let which is block scoped so there will be separate i for each for loop iteration.
1) If you want to output consective 3 so you can declare i outside of for-loop, then there will be only single i with the value 3.
function func2() {
let i;
for (i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 2000);
}
}
func2();
2) If you want same lexical scope then you can also use var here,
var is function scoped, So there will only be one i
function func2() {
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 2000);
}
}
func2();

Related

setTimeout in javascript does not show updated value

why am I getting the outpupt 10 instead of 20. Why the setTimeout is not taking the latest value?
var a=10;
function foo(myvar){
console.log(myvar);
}
setTimeout(foo,1000,a);
a=20;
if I put setTimeout in a loop then it consoles the latest value
for (var i = 0; i < 3; i++) {
setTimeout(function log() {
console.log(i);
}, 1000);
}
3 3 3
This is because your setTimeout thread was initialized when the value for variable a is 10. There after you updated the value of variable a. But the value of parameter is still the old value 10.
var a = 10;
function foo(myvar) {
console.log('Value for param ', myvar);
console.log('Value for a ', a );
console.log('Thread Executed');
}
console.log('Thread Started');
setTimeout(foo, 1000, a);
a = 20;
console.log('Variable Updated');
What is happening in the other case was you are logging the value of the index i which is in the for loop. At the time of execution of console, the value of i will be the last value, which is 3
One issue is - you're not comparing like for like code
var a=10;
function foo(myvar){
console.log(myvar);
}
setTimeout(foo,10,a);
a=20;
is comparable to
var i;
function log(myvar) {
console.log(myvar);
}
for (i = 0; i < 3; i++) {
setTimeout(log, 10, i);
}
Whereas
for (var i = 0; i < 3; i++) {
setTimeout(function log() {
console.log(i);
}, 10);
}
Is exactly equivalent to
var i;
function log() {
console.log(i);
}
for (i = 0; i < 3; i++) {
setTimeout(log, 10);
}
and comparable to
var a=10;
function foo(){
console.log(a);
}
setTimeout(foo,10);
a=20;
You are passing a by value in the call to setTimeout(). Therefore no matter how many times you change the value of a after calling setTimeout() you'd not get any of the new values.
The following snippet would work:
var a=10;
function foo(){
console.log(a);
}
setTimeout(foo,1000);
a=20;
Because a is captured as part of the closure(foo())'s calling context. Notice that a is not passed to setTimeout() explicitly.
The second example you gave appears to work to you but it is actually exactly the same as the original sample it's just that you pass three different values - 1, 2, and 3 - to three consecutive calls to setTimeout(). If you added an if-statement inside the loop to ensure that you only call setTimeout() when i == 1 you'd see exactly the same behavior as in your first snippet.

Closure timeout according to elapsed seconds

I'm studying closures but I don't understand why the second is always 1 instead of 2, 3, 4, 5 respectively. What I understand is here that since each function (even iife) has its own scope. Each i variable is captured its own stack actually that's why it allows changes of values of i though had been var used.
In the same way, setTimeout should catch each different i variables having 1,2,3,4,5 (seconds) respectively. But, it doesn't seem such as seen in the image. Could you help?
Maybe it is helpful that i is not a free variable for
setTimeout. (no idea it is of any relation)
Code
for (let i = 1; i <= 5; i++) {
(function (i) {
setTimeout(function () {
console.log(i)
}, i * 1000)
})(i)
}
I think the use of let and an IIFE is throwing you off. Using an IIFE creates a new scope for the function parameters. A less known fact is that using let in a loop creates a new scope on every iteration.
e.g.
Using var and an IIFE.
// 'i' is declared here once
for (var i = 1; i <= 5; i++) {
// 'i' is declared again and takes a new scope every time the function is executed
(function (i) {
setTimeout(function () {
console.log(i) // references the nearest-scoped 'i'
}, i * 1000)
})(i)
}
// => 1, 2, 3, 4, 5
Using var and no IIFE
// 'i' is declared here once
for (var i = 1; i <= 5; i++) {
setTimeout(function () {
console.log(i) // references nearest-scoped 'i'
}, i * 1000)
}
// => 6, 6, 6, 6, 6
Using let and no IIFE
// 'i' is given a new scope in every iteration
for (let i = 1; i <= 5; i++) {
setTimeout(function () {
console.log(i)
}, i * 1000)
}
// => 1, 2, 3, 4, 5
Using let and an IIFE. This is exactly your example.
// 'i' is given a new scope in every iteration
for (let i = 1; i <= 5; i++) {
// 'i' is declared again and takes a new scope every time the function is executed
(function (i) {
setTimeout(function () {
console.log(i) // references the nearest-scoped 'i'
}, i * 1000)
})(i)
}
// => 1, 2, 3, 4, 5
I don't understand too how the engine works, albeit i gives results as expected. I wouldn't expect persistently 1 second elapsing.
for (let i = 1; i <= 5; i++) {
(function (i) {
setTimeout(function () {
console.log(i)
}, (console.log(i),i * 1000))
})(i)
}
In a comment you've said:
I expect that 1 second later 1, 2 secs later 2, 3 secs later 3, 4 secs later 4, 5 secs later 5. But 1,2,3,4,5 are printed 1 second elapsed each time as in the image
That would be what would happen if you started the timer for 2 after the callback for timer 1 was called, but that's not what the code does. The code sets up the five timers all right away — a timer to show 1 after one second, a timer to show 2 after two seconds, a timer to show 3 after three seconds, etc. Since they all start at the same time, you see 1 after one second, 2 a second after that (two seconds after it was scheduled), 3 a second after that (three seconds after it was scheduled), etc.
To see that, let's show when we start each timer, and also how long it's been since the previous one fired. First, though, you don't need your IIFE, because you're using let in a for statement, so each loop iteration gets its own i variable. So here's your original code without the IIFE but adding the message when each timer starts and showing how long it was between when the timer started and when it fired; this will still just do them roughly one second apart instead of doing what you said you want:
// Still doesn't do what you want, but shows that the IIFE isn't needed
// and when each timer is set up
let previousFired = Date.now();
for (let i = 1; i <= 5; i++) {
console.log(`setting timer to show ${i} in ${i * 1000}ms`);
const started = Date.now();
setTimeout(function () {
console.log(`${i} - fired after ${Date.now() - started}ms (${Date.now() - previousFired}ms after last)`);
previousFired = Date.now();
}, i * 1000);
}
If you want to wait one second to see 1, then wait a further two seconds to see 2, etc., either don't start the next timer until the previous one fires, or schedule them later. The latter is simpler, so let's do that:
let total = 0;
let previousFired = Date.now();
for (let i = 1; i <= 5; i++) {
total += i;
console.log(`setting timer to show ${i} in ${total * 1000}ms`);
const started = Date.now();
setTimeout(function () {
console.log(`${i} - fired after ${Date.now() - started}ms (${Date.now() - previousFired}ms after last)`);
previousFired = Date.now();
}, total * 1000);
}
You can handle async loops awaiting a timedout Promise, then you can call a external function:
function f(i) {
console.log(i)
}
const runLoop = async () => {
for(let i = 1; i <= 5; i++){
await new Promise(resolve => setTimeout(resolve, i * 1000))
f(i * 1000);
}
}
runLoop()

Block scoping in JavaScript for loops?

Why does this code print 6 with VAR but "i" and the incremented for loop value with LET?
for (var i = 1; i <= 5; i++) {
setTimeout(function () {
console.log("i " + i);
}, i * 1000)
}
Variables defined as var is automatically moved to the top.
Even if you define it like this:
for (var i = 1; i <= 5; i++) {
setTimeout(function () {
console.log("i " + i);
}, i * 1000)
}
it is used as if you had written
var i;
for (i = 1; i <= 5; i++) {
setTimeout(function () {
console.log("i " + i);
}, i * 1000)
}
Since i is now like a global variable, the for-loop executes and leaves the variable with value 6. When the function is called by setTimeout in the future, the value is 6 for all calls.
You can read more about let and var.
The difference between var and let is the scope:
var scope:
Declared variables are constrained in the execution context in which they are declared. Undeclared variables are always global.
let scope:
Variables declared by let have as their scope the block in which they are defined, as well as in any contained sub-blocks.
When you run your example with var, the loop will execute quickly and end before the setTimeout() functions starts executing, and when they execute, they get the current value of the i variable which is 6.
When you run your example with let, the loop will also execute quickly and end before the setTimeout() functions starts executing. However this time because the i variable is scoped to the loop, it will keep each value in the context memory for each setTimeout() function call.
To achieve the same effect with 'var', you can create a different context using a closure to preserve the value, like what let does:
for (var i = 1; i <= 5; i++) {
(function(i) {
setTimeout(function() {
console.log("i " + i);
}, i * 1000);
})(i);
}

Can not set timeout in Node.js loop [duplicate]

This question already has answers here:
setTimeout in Node.js loop
(8 answers)
Closed 5 years ago.
Here, i tried to set timeout for every iteration but i could not make it because nature of nodejs. Is there any way to do that?
Thanks in advance
for (var i = 1; i <= 1000; i++) {
setTimeout(function () { console.log('something') }, 3000);
}
It works but it schedules all timeouts at the same time.
If you want to schedule them at 3 sec intervals then use:
for (var i = 1; i <= 1000; i++) {
setTimeout(function () { console.log('something') }, i * 3000);
}
If you want to use i inside of your timeout callbacks, use let instead of var like this:
for (let i = 1; i <= 1000; i++) {
setTimeout(function () { console.log('something', i) }, i * 3000);
}
As you can see with var it would print 1001 for every line:
for (var i = 1; i <= 1000; i++) {
setTimeout(function () { console.log('something', i) }, i * 3000);
}
And by the way, you can simplify it with arrow function syntax:
for (let i = 1; i <= 1000; i++) {
setTimeout(() => console.log('something', i), i * 3000);
}
Another way to do it would be to do something like this - instead of scheduling all 1000 timeouts at the same time, create an interval:
(() => {
let i = 0;
setInterval(() => {
i++;
console.log('something', i);
}, 3000);
})();
The outer closure is to keep the i variable from being visible in the outer scope. OR you can use something like this:
(() => {
let i = 0;
let f = () => {
i++;
console.log('something', i);
setTimeout(f, 3000);
};
setTimeout(f, 3000);
})();
In the last example the function that is invoked as a timeout callback schedules itself every time it finishes.
There are many ways to do it and all have some pros and cons.
For example you shouldn't use setInterval if your callback could potentially run longer than the interval between invocations. It will not be a problem when you use setTimeout and schedule yourself in every callback but on the other hand you may have less precision in the intervals that way. You need to test what works best for you.
for (var i = 1; i <= 10; i++) {
wait('something', i, 10);
}
function wait(msg, i, length){
setTimeout(function ()
{
console.log(msg) ;
}, (i * 3000));
}
try something like this.
Your code actually works but you wait 3 seconds for all which will wait about 1 ms before the next iteration is done runs
I think what you're really looking for is setInterval.
let runMeEveryThreeSeconds = function() {
console.log('hello!')
}
let threeSecondTimer = setInterval(runMeEveryThreeSeconds, 3000)
The timer will run forever, and every three seconds the word 'hello!' will be printed to your console. When you are ready to cancel the timer, you will need to:
clearInterval(threeSecondTimer)

Why is count correctly incremented

Consider this simple example:
var count = 0;
for (var i = 0; i < 4; i++ ) {
setTimeout(function() {
console.log(i, count++);
}, i * 200);
}
which outputs the following
4 0
4 1
4 2
4 3
I would guess that i always resolves to 4 because the setTimeout callback closes over the variable I but I can't figure out why the same doesn't hold true for count?
var count = 0;
for (var i = 0; i < 4; i++ ) {
setTimeout(function() {
console.log(i, count++);
}, i * 2000 );
}
The variable i is incremented by your for loop, and ends up with the value 4 before any of the timeout handlers run. The variable count, on the other hand, is only incremented inside the timeout handlers. When the first timeout handler fires, count will still be 0.
As the lads before me said, It's because the loop completes before the timeout is activated.
One way around this would be to put your timeout in a different function, and pass the i to it from within the loop. This ensures that for each iteration, the function is passed the correct i
This is also why count is set to the expected value; it lies outside of the loop's scope.
for (var i = 0; i < 4; i++) {
doSomething(i);
};
function doSomething(i) {
setTimeout(function() {
console.log(i);
}, i * 2000);
};

Categories

Resources