How can I stop nested setTimeout with clearTimeout? - javascript

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

Related

Pomodoro Timer goes negative and counts up

I have a pomodoro timer that is supposed to count down the "workminutes" a user has input and then the break minutes and then loop. The timer starts to count down WorkMinutes like it should and then break minutes(like it should), it then restarts and counts down the workminutes again like it should BUT when its done with that and it comes to the breakminutes a second time instead of counting down from eg. 1 minute it counts from -1 minute and up, so -1 minute and one second and so on. I'm a complete beginner in JavaScript so it would be very nice if you kept that in mind, any help i greatly appreciated. Here is the JavaScript code:
// we need some variables to store the work and break minutes
var workSeconds = "120", breakSeconds = "60";
// and a referens to interval
var xInterval;
var audio = new Audio('Bell_finished.mp3');
// start function
function start() {
xInterval = setInterval(workCountDown, 1000);
}
// stop function
function stop() {
clearInterval(xInterval);
}
// reset function; calls stop, save which re-stores the values of user inputs and then starts again.
function reset() {
stop();
save();
start();
}
// save function that saves the values of user inputs
function save() {
workSeconds = parseInt(document.getElementById("TaskTime").value)*60;
breakMinutes = parseInt(document.getElementById("BreakTime").value)*60;
}
// working count down function
function workCountDown() {
// counting down work seconds
workSeconds--;
// showing work seconds in "0:0" format:
document.getElementById("timer").innerText = Math.floor((workSeconds / 60)).toString() + ":" + (workSeconds % 60).toString();
// if workSeconds reaches to zero, stops the workInterval and starts the breakInterval:
if (workSeconds == 0) {
audio.play();
console.log("relaxing...");
clearInterval(xInterval);
xInterval = setInterval(breakCountDown, 1000);
}
}
// breaking count down function
function breakCountDown() {
// counting down break seconds
breakSeconds--;
// showing break seconds in "0:0" format:
document.getElementById("timer").innerText = Math.floor((breakSeconds / 60)).toString() + ":" + (breakSeconds % 60).toString();
// if breakSeconds reaches to zero, stops the breakInterval, resets the variables to initial values by calling save function and starts the workInterval again:
if (breakSeconds == 0) {
audio.play();
console.log("ready to work...");
reset();
}
}
as I said in the comment it works fine, just change breakMinutes to breakSeconds inside save function. here is an implementation of your code. you can run the snippet here and see the result
// we need some variables to store the work and break minutes
let workSeconds = "120",
breakSeconds = "60";
// and a referens to interval
let xInterval;
let isStarted = false;
// start function
function start() {
xInterval = setInterval(workCountDown, 1000);
}
// stop function
function stop() {
clearInterval(xInterval);
}
// reset function; calls stop, save which re-stores the values of user inputs and then starts again.
function reset() {
stop();
save();
start();
}
// save function that saves the values of user inputs
function save() {
workSeconds =
parseInt(document.getElementById("TaskTime").value || 120, 10) * 60;
breakSeconds =
parseInt(document.getElementById("BreakTime").value || 60, 10) * 60;
}
// working count down function
function workCountDown() {
// counting down work seconds
workSeconds--;
// showing work seconds in "0:0" format:
document.getElementById("timer").innerText =
Math.floor(workSeconds / 60).toString() +
":" +
(workSeconds % 60).toString();
// if workSeconds reaches to zero, stops the workInterval and starts the breakInterval:
if (workSeconds === 0) {
console.log("relaxing...");
clearInterval(xInterval);
xInterval = setInterval(breakCountDown, 1000);
}
}
// breaking count down function
function breakCountDown() {
// counting down break seconds
breakSeconds--;
// showing break seconds in "0:0" format:
document.getElementById("timer").innerText =
Math.floor(breakSeconds / 60).toString() +
":" +
(breakSeconds % 60).toString();
// if breakSeconds reaches to zero, stops the breakInterval, resets the variables to initial values by calling save function and starts the workInterval again:
if (breakSeconds === 0) {
console.log("ready to work...");
reset();
}
}
const startButton = document.getElementById("start-btn");
startButton.addEventListener("click", (e) => {
isStarted = !isStarted;
if (isStarted) {
save();
start();
startButton.textContent = "Stop";
} else {
stop();
startButton.textContent = "Start";
document.getElementById("timer").innerText = 0;
}
});
<label>Work Time: <input type="number" id="TaskTime" value="1" /></label>
<label>Break Time: <input type="number" id="BreakTime" value="1" /></label>
<div id="timer">0</div>
<button id="start-btn">Start</button>

Increment value in time

I am looking to increment the value of "time" with 0.01 each 10 miliseconds until it gets to the desired value. Right now it just increases it instantly to the conditioned value.
var time = 0;
function animate() {
decreaseIncrement = -0.78;
increaseIncrement = 0.78;
if (
(document.getElementById("but5").onclick = function () {
if (time < increaseIncrement) {
do {
time += 0.01;
} while (time < increaseIncrement);
}
})
)
if (
(document.getElementById("but3").onclick = function () {
if (decreaseIncrement < time) {
do {
time -= 0.01;
} while (decreaseIncrement < time);
}
})
)
increaseIncrement = time + increaseIncrement;
decreaseIncrement = time + decreaseIncrement;
}
https://jsfiddle.net/2epqg1wc/1/
You can solve that problem using setInterval which repeatedly runs a task every x milliseconds until you cancel it. Below code reduces the value to 0 in 0.01 steps with a step performed every 10 milliseconds.
var value = 1.0;
var decrement = 0.01;
function decreaseAnimation() {
var interval = setInterval(() => {
value -= decrement;
console.log(value);
if (value <= 0) {
clearInterval(interval);
}
}, 10);
}
decreaseAnimation();
You have 3 options:
requestAnimationFrame (rAF)
setTimeout/setInterval (sTo)
messageChannel
The first 2 options are more straightforward but they will lack the precision, because rAF fires every 17 milliseconds (assuming 60Hz) and sTO will fire at most 4ms after 4 successive recursions. Usually rAF is preferred over sTo because of better reliability in timing of firing these callbacks. Use sTO as a fallback if rAF is not supported.
Here is an implementation from a library for similar purposes:
var rafx = require("rafx");
rafx.async({ //create a ledger object to store values
curr_time:0,
desired:Math.random(),
frames:0
}).animate(function(obj){
//obj is the ledger above
//increment obj.frames here if you want to
return obj;
},).until(function(obj){
obj.frames++;
obj.curr_time = obj.frames * 17 / 10 * 0.01;
return obj.curr_time >= obj.desired;
}).then(function(obj){
console.log("sequence ended with values:" + JSON.stringify(obj));
});
You can copy paste the code above here and test it.
The last option uses MessageChannel to post message between ports, which gives extremely high precision because it is fired at the next event loop. You can combine this with performance.now to determine whether to increment your time or not.
Disclosure: I am the author of the aforementioned lib.

JavaScript: setInterval() doesn't produce expected output

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

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.

How to generate random duration value of second argument ( duration) in setInterval function

How to generate random duration value of second argument ( duration) in setInterval function.
//such as
var timerId = setInterval( timer_counter,getRandomInt(5,60),number,slatt);
var n = 10, // max value
r = Math.floor(Math.random() * n) + 1; // random number (1-10)
setInterval(function(){
timer_counter();
}, r * 1000); // to milliseconds
You're looking for Math.random() I believe (coupled with Math.floor).
Note: If r is (for example) 3, it will execute every 3 seconds for the life of that interval. If you want it to change, you need to use a setTimeout and change the timeout on every call. So to do that:
function worker(){
// the code that should be executed
}
function repeat(){
var n = 10; // every 1-10 seconds
setTimeout(function(){
worker();
repeat();
}, (Math.floor(Math.random() * n) + 1) * 1000);
}();
And to give you that getRandomInt function:
function getRandomInt(nMax, nMin){
nMax = nMax || 10;
nMin = nMin || 0;
return Math.floor(Math.random() * (nMax - nMin + 1)) + nMin;
}

Categories