JavaScript: setInterval() doesn't produce expected output - javascript

Please take a look at my code below, I don't know why it produce expected output. I think the way I used setInterval() and setTimeout is wrong. Somehow the process doesn't go by 1 order from top to bottom. It seems like there are 3 threads run in parallel. How can I fix it? Thank you.
(function() {
var nums = [1, 2, 3];
nums.forEach(
(e) => {
console.log(e);
var frame = 0;
let loop = setInterval(function() {
if (frame === 3)
clearInterval(loop);
console.log(e + " frame " + frame);
frame++;
}, 1000);
let wait = setTimeout(function() {
console.log(e + " 2 second passed");
}, 2000);
}
)
})();
Expected output:
1
1 frame 0
1 frame 1
1 frame 2
1 2 seconds passed
2
2 frame 0
2 frame 1
2 frame 2
2 2 seconds passed
3
3 frame 0
3 frame 1
3 frame 2
3 2 seconds passed
Actual output:
1
2
3
1 frame 0
2 frame 0
3 frame 0
1 frame 1
1 2 second passed
2 frame 1
2 2 second passed
3 frame 1
3 2 second passed
1 frame 2
2 frame 2
3 frame 2
1 frame 3
2 frame 3
3 frame 3

Because Javascript is asynchronous, you need some way of waiting for each of the intervals and the timeout to complete before running the next.
One way of doing this is by using async/await and wrapping the intervals and timeout in a promise.
(async function() {
var nums = [1, 2, 3];
for (const e of nums) {
console.log(e);
let frame = 0;
await new Promise(resolve => {
let loop = setInterval(function() {
if (frame === 3) {
clearInterval(loop);
resolve();
} else {
console.log(e + " frame " + frame);
frame++;
}
}, 100);
})
await new Promise(resolve => {
let wait = setTimeout(function() {
console.log(e + " 2 second passed");
resolve();
}, 200);
})
}
})();

No idea what you going to accomplished with this code. but please try with below approach. you can console log what you asked. Do changes as your favor,
let nums = [1, 2, 3];
const timesecs = 1000;
const timeOut = (num) => {
setTimeout(
() => {
console.log(num);
nums.forEach(
(item, index) => {
console.log(num + " frame " + index);
}
)
//console.log(`${num} ${num+1} seconds passed`);
console.log(`${num} 2 seconds passed`);
},
num * timesecs
)
}
nums.forEach((num) => {
timeOut(num);
});

Javascript doesn't work this way. You need to understand the concept of ASYNC operations and callbacks first. Aysnc operations, like setTimeout and setInterval, do not wait for their callback functions to finish before moving to the next line of the code. They just move the execution cursor to the next line. Your setInterval function will finish its callback execution after 1000 milliseconds.
There are new functionalities are added like await and async function. You may want to look into them to achieve what you want.
The for loop you are running should be inside the interval rather than what you are doing.
(function () {
var nums = [1, 2, 3];
var ind = 0;
let loop = setInterval(function(){
if(ind === 2){
clearInterval(loop);
}
console.log(nums[ind]);
nums.forEach(e => {
console.log(nums[ind] + " frame " + e);
});
console.log(nums[ind] + " 2 seconds passed");
ind++;
}, 2000);
})();

You have a forEach loop that will loop 3 times. On the first iteration, it will:
console.log the frame (1)
create an interval that will execute in 1 second
create a timeout that will execute in 2 seconds
Then the second iteration of the loop happens immediately after the first iteration so it will again:
console.log the frame (2)
create another new second interval that will execute in 1 second
create another new second timeout that will execute in 2 seconds
Finally the third iteration will occur, immediately and it will:
console.log the frame (3)
create another third new interval that will execute in 1 second
create another third new timeout that will execute in 2 seconds
Next, all three of your newly created intervals will execute about 1 second after the loop finishes. Each interval will execute very slightly behind the previous interval. And each one contains a "closure" around the variable frame (i.e. when they were created, they all "captured" frame when it was set to 0 so they all console.log(0).
On the next second, each of the 3 intervals will attempt to run again (now each with frame === 1), and the 3 timeouts will also attempt to run. Note that each timeout also has formed a "closure", locking in the value of e at the time it was created. You end up getting a bit of staggering of intervals executing, mixed with timeouts executing.
The 3 timeouts only ever happen once each.
The remainder of the output is the set of 3 intervals successively executing, with a 2 second gap between each set.
You could achieve your output by just using one interval (with no loop), set to fire every second and print something. I'm not sure of the requirements regarding how many seconds apart you need these statements to be printed, so I cannot produce the exact code that you need but here's something that produces your desired output with my best guess at the timing:
var num = 1;
var frame = 0;
var loop = setInterval( function() {
if (frame === 0) {
console.log(num);
}
if (frame >= 0 && frame <= 2) {
console.log(num + " frame " + frame);
}
if (frame === 4) {
console.log(num + " 2 seconds passed");
num++;
frame = -1;
}
if (num > 3) {
clearInterval(loop);
}
frame++;
}, 1000);

Related

How do I modify this code to create a loop inside a loop?

I found this script from a website. The loop ends when it has followed 40 Instagram accounts at a 2-second interval. How do I modify the script so it waits for 10 minutes before repeating the same loop for 3 times? Basically, it goes:
Follow an account and wait for 2 seconds X40
Wait for 10 minutes
Follow an account and wait for 2 seconds X40
Wait for 10 minutes
Follow an account and wait for two seconds X40
Wait for 10 minutes
---LOOP ENDS---
var TagFollow = document.getElementsByTagName("button");
var SearchFollow = "Follow";
var foundFollow;
function clickfollow(){
for (var i = 0; i < TagFollow.length; i++) {
if (TagFollow[i].textContent == SearchFollow) {
foundFollow = TagFollow[i];
foundFollow.click();
break;
}
}
}
var i = 1;
function myLoop() {
setTimeout(function() {
console.log(new Date().toLocaleTimeString());
clickfollow();
//document.getElementsByTagName("ul")[3].scrollIntoView(false
i++;
if (i < 41) {
myLoop();
}
}, 2000)
}
myLoop();
Thanks in advance!
You do not need nested loops. You can "linearize" them in single loop (I do not recall the correct therm now). So basically you want loop of 3 * 40 = 120 and after each 40 you need to use increased delay of 10 * 60 * 1000 = 600000. You help yourself by using modulo function i%40. Because you start with 1, the each 40th iteration is when remainder of i divided with 40 is zero.
So basically change if (i < 41) to if (i < 121) and }, 2000) to }, i%40 === 0 ? 600000 : 2000)

JavaScript for Loop timeout

I seen this loop , and I don't understand how after the second loop ( when I = 2 ) the timeout is still 4 second and not 7 seconds.
First loop , i = 0 , x = 0 , and the output is 0 after 1 sec - i understand why
Second loop , i = 1 , x = 1 , and the output is 1 after 4 sec - i understand why 3000 X 1 + 1000 = 4000 ( 4 sec )
Third loop , i = 2 , x = 2 , the output is 2 after 4 sec - why 4 sec and not 7 ? 3000 X 2 + 1000 is 7000 ( 7 sec ) but why i get the output after 4 sec and not 7 ?
for (var i = 0; i <= 10; i++) {
(function(x) {
setTimeout(function() {
console.log(x);
}, 1000 + (3000 * x));
})(i);
}
The current execution plan is as follows:
As you can see, all the executions were relative to the starting point (almost).
If you wanted the executions to happen one after the other (0 after 1 second, 1 after 5 seconds, 2 after 12 seconds), then do this:
function compoundedInterval(x, limit) {
if (x > limit) {
return;
}
setTimeout(function() {
console.log(new Date(), x);
compoundedInterval(x + 1);
}, 1000 + (3000 * x));
}
compoundedInterval(0, 10);
I've updated the console.log to add the time so we can see the actual gaps between executions.
I now have the ff. results:
The timeout for i, x = 2 is 7 seconds. That is why it happens 3 seconds after i, x = 1 fires. This function as written does not register the next timer when the previous timer fires, it registers all 10 within a few milliseconds of being called, They go off at 4, 7, 10, 13, 16, 19, 22, 25, 28, and 31 seconds.
This one, however, will have a longer delay between each one. The key is registering the next timer as the current one is firing instead of all 10 at once.
function timeRecurse (i=0) {
var next = () => timeRecurse(i +1)
console.log(i);
return (i < 10) ? setTimeout(next, 1000 + (3000 * i)): null;
}
timeRecurse();
There is 7 seconds delay for third iteration, but from start of the script, not from second iteration. It is because of asynchronous nature of javascript.
All timeouts will start at the same time and each console output will happen 3 seconds after previous one.
Check javascript event loop for more information.
For example, this video pretty nicely explains that topic in my opinion.

How can I stop nested setTimeout with clearTimeout?

I have a function everyXsecsForYsecs that will accept three arguments: a function, an interval time in seconds, and a total time in seconds.
everyXsecsForYsecs should invoke the given function every X * 1000 milliseconds, yet then stop invoking the function after Y * 1000 milliseconds. Addition to this, here is a simple callback function for everyXsecsForYsecs:
function sayHowdy(){
console.log('Howdy');
}
Then I call everyXsecsForYsecs as such:
everyXsecsForYsecs(sayHowdy, 1, 5);
So what I expect is to see 5 'Howdy' in the console, then function to stop. But what happens is that, function prints 'Howdy' for ever. Here is how I implemented everyXsecsForYsecs,
function everyXsecsForYsecs(callback, X, Y) {
x_mili = X * 1000;
y_mili = Y * 1000;
let id = setTimeout(function run(){
callback();
id = setTimeout(run, x_mili);
}, x_mili);
setTimeout(clearTimeout, y_mili, id);
}
I am suspicious about how I use clearTimeout with nested setTimeout,
What I am missing exactly?
By the time
setTimeout(clearTimeout, y_mili, id);
runs, id contains the timer id of the first outer setTimeout call. Cancelling that won't really help. If you'd replace it with:
setTimeout(() => clearTimeout(id), y_mili);
it'll clear the timeout with the id at that time, as you evaluate id when the timeout is done, and not when it get's started.
I'd write it as:
function everyXsecsForYsecs(callback, X, Y) {
let count = Math.floor(Y / X);
function recurring() {
callback();
if((count -= 1) >= 0)
setTimeout(recurring, X * 1000);
}
setTimeout(recurring, X * 1000);
}
let firstId = setTimeout(sayHowdy, 1000)
will call sayHowdy after 1000ms and store the timeout id within firstId
clearTimeout(firstId)
if this is called, the timeout referenced by the id will be cleared (no matter if it already is over or not)
But the question actually is, why you would want to clear the timeout, it's no interval, so you probably are in the wrong box.
have a look at this snippet, it does not repeat for seconds, but x times with recursion:
function fireAllXSecsYTimes(callback, fireAfterSeconds, counter) {
if (counter === 0) {
return;
}
setTimeout(() => {
callback();
counter--;
fireAllXSecsYTimes(callback, fireAfterSeconds, counter);
}, fireAfterSeconds * 1000)
}
what you asked for:
function fireAllXSecsForYSecs(callback, fireAfterSeconds, remainingSeconds) {
if (remainingSeconds <= 0) {
return;
}
setTimeout(() => {
callback();
fireAllXSecsForYSecs(callback, fireAfterSeconds, remainingSeconds - fireAfterSeconds);
}, fireAfterSeconds * 1000)
}
called with fireAllXSecsForYSecs(() => console.log('howdy'), 2, 5)
it will log 'howdy' 3 times, because on third execution, remainingSeconds still has 1 left. If you want to prevent this, just return if remainingSeconds <= 0 || remainingSeconds < fireAfterSeconds
Pass the reference not the value.
function sayHowdy() {
console.log('Howdy');
}
function everyXsecsForYsecs(callback, X, Y) {
x_mili = X * 1000;
y_mili = Y * 1000;
let id = setTimeout(function run() {
callback();
id = setTimeout(run, x_mili);
}, x_mili);
setTimeout(() => clearTimeout(id), y_mili);
//here you need to pass the reference to the id not the value
//which is constantly changing
}
everyXsecsForYsecs(sayHowdy, 1, 5);

countdown from n to 0 in given time, negative end value

I am working on simple script that should animate given value (for example 6345.23) to 0 by counting it down, it should also end up at 0 if specified amount of time have passed (for example 2 seconds.
I started by simple logic:
given config: initial value, time in sec, interval
time is given in seconds so convert it to milliseconds
calculate amount of ticks by dividing time in ms by interval
calculate amount of decreased value per tick by dividing initial value by amount of ticks
once above are known we can simply do: (simple model, not actual code)
intId = setInterval(function() {
if(ticks_made === amount_of_ticks) {
clearInterval(intId);
} else {
value -= amount_per_tick;
// update view
}
}, interval);
actual code:
var value = 212.45,
time = 2, // in seconds
interval = 20; // in milliseconds
var time_to_ms = time * 1000,
amount_of_ticks = time_to_ms / interval,
amount_per_tick = (value / amount_of_ticks).toFixed(5);
var start_time = new Date();
var ticks_made = 0;
var intId = setInterval(function() {
if(ticks_made === amount_of_ticks) {
console.log('start time', start_time);
console.log('end time', new Date());
console.log('total ticks: ', amount_of_ticks, 'decresed by tick: ', amount_per_tick);
clearInterval(intId);
} else {
value = (value - amount_per_tick).toFixed(5);
console.log('running', ticks_made, value);
}
ticks_made++;
}, interval);
Link do fiddle (in console you can observe how it works)
If you set time to 2 (2 seconds) its ok, but if you set time to for example 2.55 (2.55 seconds) it doesnt stop at all at 0, its passing by and going indefinitely in negative values.
How i can fix it so no matter what is set in seconds its always go precisly one by one until reaches perfectly 0?
var value = 212.45,
time = 2, // in seconds
interval = 20; // in milliseconds
var time_to_ms = time * 1000,
amount_of_ticks = time_to_ms / interval,
amount_per_tick = (value / amount_of_ticks).toFixed(5);
var start_time = new Date();
var ticks_made = 0;
var intId = setInterval(function() {
if(ticks_made === amount_of_ticks) {
console.log('start time', start_time);
console.log('end time', new Date());
console.log('total ticks: ', amount_of_ticks, 'decresed by tick: ', amount_per_tick);
clearInterval(intId);
} else {
value = (value - amount_per_tick).toFixed(5);
console.log('running', ticks_made, value);
}
ticks_made++;
}, interval);
You're relying on ticks_made === amount_of_ticks being an exact match. Chances are, due to rounding, you won't get an exact match, so you'd be better off doing:
if(ticks_made >= amount_of_ticks) {
kshetline's answer correctly addresses why you get into negative values. When dealing with fractional IEEE-754 double-precision binary numbers (in the normal range, or even whole numbers in very high ranges), == and === can be problematic (for instance, 0.1 + 0.2 == 0.3 is false). Dealing with values as small as the fractional values here are, accumulated imprecision is also a factor. It's inevitable to have to fudge the final step.
But there's a larger issue: You can't rely on timers firing on a precise schedule. Many, many things can prevent their doing so — other UI rendering work, other scripts, CPU load, the tab being inactive, etc.
Instead, the fundamental technique for animation on browsers is:
Update when you can
Update based on where you should be in the animation based on time, not based on how many times you've animated
Use requestAnimationFrame so your update synchronizes with the browser's refresh
Here's your code updated to do that, see comments:
// Tell in-snippet console to keep all lines (rather than limiting to 50)
console.config({maxEntries: Infinity});
var value = 212.45,
time = 2.55, // in seconds
time_in_ms = time * 1000,
amount_per_ms = value / time_in_ms,
interval = 100 / 6, // in milliseconds, ~16.66ms is a better fit for browser's natural refresh than 20ms
ticks_made = 0;
// A precise way to get relative milliseconds timings
var now = typeof performance !== "undefined" && performance.now
? performance.now.bind(performance)
: Date.now.bind(Date);
// Remember when we started
var started = now();
// Because of the delay between the interval timer and requestAnimationFrame,
// we need to flag when we're done
var done = false;
// Use the interval to request rendering on the next frame
var intId = setInterval(function() {
requestAnimationFrame(render);
}, interval);
// About half-way in, an artificial 200ms delay outside your control interrupts things
setTimeout(function() {
console.log("************DELAY************");
var stop = now() + 200;
while (now() < stop) {
// Busy-loop, preventing anything else from happening
}
}, time_in_ms / 2);
// Our "render" function (okay, so we just call console.log in this example, but
// in your real code you'd be doing a DOM update)
function render() {
if (done) {
return;
}
++ticks_made;
var elapsed = now() - started;
if (elapsed >= time_in_ms) {
console.log(ticks_made, "done");
done = true;
clearInterval(intId);
} else {
var current_value = value - (amount_per_ms * elapsed);
console.log(ticks_made, current_value);
}
}
/* Maximize in-snippet console */
.as-console-wrapper {
max-height: 100% !important;
}
If you run that, then scroll up to the "************DELAY************" line, you'll see that even though rendering was held up by "another process", we continue with the appropriate next value to render.
It would make sense to convert the result of .toFixed() to a number right away:
let amount_per_tick = +(value / amount_of_ticks).toFixed(5);
let value = +(value - amount_per_tick).toFixed(5);
(note the + signs)
Then you will never have to worry about type coercion or anything, and instead just focus on math.

Function is called more than once

I am creating a game similar to Pacman. The game board is held in an array called "testLevel." Here, I am trying to code the ghosts and make them move one square per 5 second. What happens is that every 5 seconds the ghost function will be called, but the program runs so fast that the function gets called multiple times within that second when I only want it to run once then not run again until another 5 seconds. How can I fix this problem. Thanks!
var testLevel = [[0,0,0,0,0,0],[0,1,0,1,1,0],[0,0,1,0,1,0],[0,0,1,0,1,0],[0,1,4,1,1,0],[0,0,0,0,0,0]];
function draw() {
background(255);
var sec = second();
if (sec % 5 == 0) {
ghost();
}
}
function ghost(){
for(b=1; b <7 ;b++){// column
for (a=5; a>-1; a--){// row
if (testLevel[a][b] == 4 && testLevel [a-1][b] !== 0){
c = a;
d = b;
printBoard();
}
}
}
testLevel[c][d] =1;
testLevel[c-1][d] = 4;
}
It sounds to me like you want to use some sort of timing function, either
setTimeout(function, milliseconds)
---Executes a function, after waiting a specified number of milliseconds.
or
setInterval(function, milliseconds)
---Same as setTimeout(), but repeats the execution of the function continuously.
(From http://www.w3schools.com/js/js_timing.asp)
In this case, setInterval(ghost, 5000) in draw() should do the trick.
Instead of looping to determine 5 seconds, use setInterval:
var testLevel = [[0,0,0,0,0,0],[0,1,0,1,1,0],[0,0,1,0,1,0],[0,0,1,0,1,0],[0,1,4,1,1,0],[0,0,0,0,0,0]];
function draw() {
background(255);
var interval = setInterval(ghost, 5000)
function ghost(){
for(b=1; b <7 ;b++){// column
for (a=5; a>-1; a--){// row
if (testLevel[a][b] == 4 && testLevel [a-1][b] !== 0){
c = a;
d = b;
printBoard();
}
}
}
testLevel[c][d] =1;
testLevel[c-1][d] = 4;
}
Note: You can use clearInterval(interval) to stop the process.

Categories