Can someone help me understand the output of the following code? - javascript

I've tried but every time I get a different weird output. Ideally, it should print 0, 1, 2, 3, 4 and come out of the loop but it doesn't happen.
function foo(iteration) {
if (iteration === 5) {
return;
}
for (let i = 0; i < 5; i++) {
console.log(i);
foo(iteration + 1);
}
}
foo(0);

To understand the current output, start by walking through your function step by step. It helps me to write it out and keep track of the values.
iteration starts at 0
it starts the for loop
i is 0 at this point.
its logged to the console.
foo is called again with value of 1
iteration is still less than 5
a new for loop starts. (step 2)
i is 0, this is a brand new loop since it's inside of a new instance of foo
In each iteration of the for loop, you call foo and while iteration is less than 5 you start a new for loop.
Here is an example of a function that does what you like:
function foo(iteration) {
while(iteration < 5) {
console.log(iteration)
iteration++
}
}
foo(0)

You don't need to use recursion AND a loop. Just the loop itself will work.
for (let i = 0; i < 5; i++) {
console.log(i);
}
Or, just recursion:
function foo(i) {
if (i >= 5) return true;
console.log(i);
foo(i + 1);
}
foo(0);
What you're doing is saying:
While foo-iteration is less than 5, console.log five times and start up FIVE NEW incremented foo-iterations. Each of those 5 new foo-iterations will console.log five times on their own and start up FIVE NEW incremented foo-iterations, and so on until the foo-iterations finally reach 5. Do you see how this tailspins out of control?

Since you are already using recursion, there is no need to have that loop. Something like this should work:
function foo(iteration) {
if (iteration < 5) {
console.log(iteration);
foo(iteration + 1);
}
}
foo(0)

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()

Why is this while statement creating an infinite loop?

Without the if statement this loops works fine, however as soon as I add the if statement it turns it into an infinite loop-why? From my understanding continue should make the loop skip an iteration and then run as normal?
let num=0;
while(num<10){
if(num===4){console.log("skipping "+num);
continue;
}
console.log(num++);
}
You need also to increment the num in the if block. Without it after the if statement it never reaches to the num++ and you never change the value of num, so it stays 4 and every time goes into the if. You can add ++ in the if statement.
let num = 0;
while(num < 10) {
if(++num === 4) {
console.log("skipping " + num);
continue;
}
console.log(num);
}
Inside your while loop when num gets incremented to 4, it enters the if block and you are not incrementing num inside if block.
Also you are using continue which skips the code in the current iteration and moves to the next iteration. This keeps on happening and num is never incremented which leads to an infinite loop.
The following code prints numbers from 0 to 9 skipping 4 as asked in the question.
let num = 0;
while(num < 10) {
if(num === 4) {
console.log("skipping " + num++);
continue;
}
console.log(num++);
}

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);
};

Looping setTimeout

I'm currently trying to wrap my head around some JavaScript.
What I want is a text to be printed on the screen followed by a count to a given number, like so:
"Test"
[1 sec. pause]
"1"
[1 sec. pause]
"2"
[1 sec. pause]
"3"
This is my JS:
$(document).ready(function() {
var initMessage = "Test";
var numberCount = 4;
function count(){
writeNumber = $("#target");
setTimeout(function(){
writeNumber.html(initMessage);
},1000);
for (var i=1; i < numberCount; i++) {
setTimeout(function(){
writeNumber.html(i.toString());
},1000+1000*i)};
};
count();
});
This is my markup:
<span id="target"></span>
When I render the page, all I get is "Test" followed by "4".
I'm no JavaScript genius, so the solution could be fairly easy. Any hints on what is wrong is highly appreciated.
You can play around with my example here: http://jsfiddle.net/JSe3H/1/
You have a variable scope problem. The counter (i) inside the loop is only scoped to the count function. By the time the loop has finished executing, is value is 4. This affects every setTimeout function, which is why you only ever see "4".
I would rewrite it like this:
function createTimer(number, writeNumber) {
setTimeout(function() {
writeNumber.html(number.toString());
}, 1000 + 1000 * number)
}
function count(initMessage, numberCount) {
var writeNumber = $("#target");
setTimeout(function() {
writeNumber.html(initMessage);
}, 1000);
for (var i = 1; i < numberCount; i++) {
createTimer(i, writeNumber);
}
}
$(document).ready(function() {
var initMessage = "Test";
var numberCount = 4;
count(initMessage, numberCount);
});
The createTimer function ensures that the variable inside the loop is "captured" with the new scope that createTimer provides.
Updated Example: http://jsfiddle.net/3wZEG/
Also check out these related questions:
What's going on under the hood here? Javascript timer within a loop
JavaScript closure inside loops – simple practical example
In your example, you're saying "2, 3, 4 and 5 seconds from now, respectively, write the value of i". Your for-loop will have passed all iterations, and set the value of i to 4, long before the first two seconds have passed.
You need to create a closure in which the value of what you're trying to write is preserved. Something like this:
for(var i = 1; i < numberCount; i++) {
setTimeout((function(x) {
return function() {
writeNumber.html(x.toString());
}
})(i),1000+1000*i)};
}
Another method entirely would be something like this:
var i = 0;
var numberCount = 4;
// repeat this every 1000 ms
var counter = window.setInterval(function() {
writeNumber.html( (++i).toString() );
// when i = 4, stop repeating
if(i == numberCount)
window.clearInterval(counter);
}, 1000);
Hope this helps:
var c=0;
var t;
var timer_is_on=0;
function timedCount()
{
document.getElementById('target').value=c;
c=c+1;
t=setTimeout("timedCount()",1000);
}
function doTimer()
{
if (!timer_is_on)
{
timer_is_on=1;
timedCount();
}
}

Categories

Resources