JS. setInterval does not increment value of variables in external scope - javascript

I trying to bring my timer update the value of all let variables. But unfortunally my setInterval fucntion does not want to do this.
Where is my problem located? Thanks
function defineInterval(intervalInHours = 1, precision = 3600, disableInterval = 900, time = 200000) {
let interval = 0
let timeLeftInInterval = 0
let timePercent = 0
let timeDisabled = 0
setInterval(() => {
interval = intervalInHours * precision;
timeLeftInInterval = (time % interval);
console.log(interval, 'interval', timeLeftInInterval, 'timeLeftInInterval ', timeDisabled, 'timeDisabled', timePercent, 'timePercent') // 3600, 2000, 1600, 59 on output
if (disableInterval >= timeLeftInInterval) {
timeDisabled = disableInterval - timeLeftInInterval;
timePercent = (timeDisabled / disableInterval) * 100;
} else {
timeDisabled = interval - timeLeftInInterval;
timePercent = (timeDisabled / (interval++ - disableInterval) * 100);
interval++
}
}, 1000)
}
defineInterval();

Possible issues:
interval = intervalInHours * precision; is getting reset to 3600 every time. Move it above setInterval.
You are not doing anything to change values in if statement, it's not impacting anything. Changing timeDisabled and timePercent is not impacting anything further. It will give same values after every timeout. Are you missing an interval++?
You are doing interval++ two times in your else statement.

Related

How to do a task in a time interval in javascript

inside JavaScript, how can I tell something to be done in a certain period of time? For example, for 5 seconds, the value of a variable will increase and it will stop after 5 seconds.
Of course, there are setinterval and settimeout, but they only do that work after that period of time is over. I want that work to be done during that time.
setInterval
You can use setInteveral, but it's mostly trash for everything I've ever attempted to use it for. In the code below, the counter will only increment to around 35 in 1 second; about 28 milliseconds per interval -
// initialize a counter
let counter = 0
// increment the counter on interval
let myprocess = setInterval(_ => counter++, 0)
// after 5 seconds, stop the process and log the counter value
setTimeout(_ => { clearInterval(myprocess); console.log(counter) }, 1000)
A more appropriate utility would be setImmediate. It is supported in Node but not in browsers yet. Polyfills are available if you plan to use this in the browser.
async generators
Above we see setInterval is quite slow. Another approach is to use an asynchronous generator. In the code below, the counter can reach over 300,000 in just 1 second; About 3.3 microseconds per interval.
async function *count(until) {
let counter = 0
while (Date.now() < until) {
yield counter++
}
}
async function main() {
for await (const n of count(Date.now() + 1000))
document.write(`${n}, `)
return "done"
}
main().then(console.log)
If the goal is to run the process as fast as possible for the fixed duration, we will skip expensive operations like document.write. In the example below the counter can reach over 1M in just 1 second; just under 1 microsecond per interval -
async function *count(until) {
let counter = 0
while (Date.now() < until) {
yield counter++
}
}
async function run(process, duration) {
let result
for await (const n of process(Date.now() + duration))
result = n
return result
}
run(count, 1000).then(console.log)
// 1045592
blocking while loop
If the loop itself is the main part of your program and you want simply want to run the counter as fast as possible for a fixed duration, use a while loop. Note this will block all other code from running for the entire duration of the loop -
function count(until) {
let n = 0
while (Date.now() < until)
n++
return n
}
setTimeout(console.log, 0, "I was blocked")
console.log(count(Date.now() + 1000))
// 36618673
// "I was blocked"
You will need to check inside some sort of loop if the time has passed, make sure the loop is async in nature or the JS main thread will have issues.
I have modified PI calculator from here Javascript: PI (π) Calculator to run for 5 seconds instead of infinite
, it's using requestAnimationFrame to keep things async->
function * generateDigitsOfPi() {
let q = 1n;
let r = 180n;
let t = 60n;
let i = 2n;
while (true) {
let digit = ((i * 27n - 12n) * q + r * 5n) / (t * 5n);
yield Number(digit);
let u = i * 3n;
u = (u + 1n) * 3n * (u + 2n);
r = u * 10n * (q * (i * 5n - 2n) + r - t * digit);
q *= 10n * i * (i++ * 2n - 1n);
t *= u;
}
}
// Demo
let iter = generateDigitsOfPi();
const tmStart = Date.now();//
let output = document.querySelector("div");
(function displayNextDigit() {
output.insertAdjacentHTML("beforeend", iter.next().value);
scrollTo(0, document.body.scrollHeight);
if (Date.now() - tmStart < 5000) requestAnimationFrame(displayNextDigit);
})();
div { word-wrap:break-word; font-family: monospace }
<div></div>
const setRunInSeconds = (callback, ms = 1000, delay = 1) => {
const intervalId = setInterval(callback, delay)
setInterval(() => {
clearInterval(intervalId)
}, ms)
}
let num = 0
setRunInSeconds(() => {
console.log('interval: ' + num)
num += 1
}, 5000, 1)

Why can't I change this global variable from function? (JavaScript)

Simple countdown project.
Desired outcome:
global variable determines default time.
then slider value overrides that variable.
PROBLEM: slider value changes locally but global value stays the same.
I've watched tutorials all day on variable scopes and still don't see what's wrong because:
Global is declared with "var" outside function: var initialMinutes = 5
attempt to update value inside function looks like this:
const change = slider.addEventListener('change', setMins)
function setMins() {
initialMinutes = slider.value
}
I have also tried with window.variable here and there to no avail.
I hope someone can help. This is becoming difficult to scope with.
const countdown = document.getElementById('countdown')
const slider = document.getElementById('slider')
const startBtn = document.getElementById('btn-startStop')
const resetBtn = document.getElementById('btn-reset')
// Event Listeners
const change = slider.addEventListener('change', setMins)
const start = startBtn.addEventListener('click', startStop)
const reset = resetBtn.addEventListener('click', resetApp)
// Time
var initialMinutes = 15
countdown.innerHTML = initialMinutes+':00'
const initialDuration = initialMinutes * 60
let time = initialMinutes * 60
let interval = null
let status = 'stopped'
function updateCountdown() {
const minutes = Math.floor(time / 60)
let seconds = time % 60
seconds = seconds < 10 ? '0'+ seconds : seconds
if( time < 1) {
clearInterval(interval)
startBtn.innerHTML = 'START'
status = 'stopped'
countdown.innerHTML = `00:00`
} else {
countdown.innerHTML = `${minutes}:${seconds}`
time--;
}
}
function startStop() {
if(status === 'stopped') {
interval = setInterval(updateCountdown, 50)
startBtn.innerHTML = 'STOP'
status = 'running'
} else {
clearInterval(interval)
startBtn.innerHTML = 'START'
status = 'stopped'
}
}
function setMins() {
initialMinutes = slider.value
countdown.innerHTML = slider.value+':00'
}
function resetApp() {
clearInterval(interval);
document.getElementById('countdown').innerHTML = '00:00'
startBtn.innerHTML = 'START'
status = 'stopped'
}
Codepen link included for clarity:
https://codepen.io/donseverino/pen/YzWBJYV
Got it! It was not a problem of scope but of variable assignment.
updateCountdown() uses time and time = initialMinutes * 60
I thought changing initialMinutes would automatically change its value inside time but it doesn't.
Reassigning time inside the function solves it.
function setMins() {
initialMinutes = slider.value
time = initialMinutes * 60
}

setInterval goes to negative

I'm buidling this pomodoro app.
https://jsfiddle.net/yvrs1e35/
I got few problems with the timer.
startBtn.addEventListener('click', function(){
minutes.innerHTML = sessionTime.innerHTML - 1
seconds.innerHTML = 59
var timer = setInterval(()=>{
if(Number(minutes.innerHTML) != 0 && Number(seconds.innerHTML) != 0){
seconds.innerHTML--
if(Number(seconds.innerHTML) == 0){
seconds.innerHTML = 59;
minutes.innerHTML--
}
}else if (Number(minutes.innerHTML) == 0 && Number(seconds.innerHTML) == 0){
clearInterval(timer)
}
},1000)
resetBtn.addEventListener('click', function(e){
e.preventDefault();
breakTime.innerHTML = 5
sessionTime.innerHTML = 25
minutes.innerHTML = "00"
seconds.innerHTML = "00"
clearInterval(timer)
})
pauseBtn.addEventListener('click', function(e){
e.preventDefault();
})
})
It works if the timer if there is more than 1 minute left on the interval.
If it goes under 1 minute, even though i have this if in the interval
else if (Number(minutes.innerHTML) == 0 && Number(seconds.innerHTML) == 0){
clearInterval(timer)
}
seconds and minutes go on negative ( after 0:0, timer shows -1:59)
I though that else if statement would stop the interval when both minutes and seconds reach 0, but it doesnt for some reason.
#also if i press startbtn multiple times, the timer starts multiple times, and the seconds go 2x 3x 4x faster, how can I stop the startbtn until the timer reaches 0:0?
Can i get any help?
.innerHTML is an expensive operation. Storing data inside the DOM like this is an antipattern; extracting it and manipulating it stringifies and de-stringifies numbers for no reason. Store state in your JS script and update the DOM content only when a rendering change is necessary. In other words, consider it write-only.
The interval runs multiple times; you'll need a flag to prevent re-triggers (or clearInterval before resetting it). Setting interval to undefined is a good way to indicate that the clock isn't running.
Lastly, setInterval with a cooldown of 1000 is a poor choice for timekeeping. It will drift quite a bit depending on scheduling interruptions and other random factors; the 1000 means "wait at least 1000 milliseconds before firing the callback". Instead, use Date for accuracy.
I'd work entirely in milliseconds and convert to minutes and seconds only for the formatted output. This follows the principle described in the first paragraph about separating presentation from logic.
Here's a proof of concept to illustrate the above points. Of course, if you're doing the pomodoro for fun, sticking to setInterval(() => ..., 1000) does make the code simpler, but I think it's instructive to see it from a couple angles if nothing else.
const padTime = t => (Math.floor(t) + "").padStart(2, 0);
const timeFmt = t => `${padTime(t / 60000)}:${
padTime(t / 1000 % 60)}`;
const run = () => {
interval = setInterval(() => {
if (interval) {
display.textContent = timeFmt(end - Date.now());
}
if (Date.now() >= end) {
clearInterval(interval);
interval = undefined;
}
}, 100);
};
let interval;
let pause;
const initialMinutes = 2;
const duration = initialMinutes * 60000;
const time = Date.now();
let end = time + duration;
const display = document.querySelector("h1");
display.textContent = timeFmt(end - time);
const [startBtn, pauseBtn, resetBtn] =
document.querySelectorAll("button");
startBtn.addEventListener("click", e => {
clearInterval(interval);
if (!interval) {
if (pause) {
end += Date.now() - pause;
pause = undefined;
}
else {
end = Date.now() + duration;
}
}
run();
});
resetBtn.addEventListener("click", e => {
clearInterval(interval);
interval = undefined;
const time = Date.now();
end = time + duration;
display.textContent = timeFmt(end - time);
});
pauseBtn.addEventListener("click", e => {
if (interval) {
pause = Date.now();
clearInterval(interval);
interval = undefined;
}
});
<h1></h1>
<div>
<button>start</button>
<button>pause</button>
<button>reset</button>
</div>
if(Number(minutes.innerHTML) != 0 && Number(seconds.innerHTML) != 0)
Changing the "&&" to "||" should fix one problem (which is that it is stuck at 0:59). If you add another condition to
if(Number(seconds.innerHTML) == 0){
where the if condition is only true, if seconds == 0 AND minutes > 0, then all problems should be solved.
Your negative time comes from starting at 0 and because of this:
minutes.innerHTML = sessionTime.innerHTML - 1
seconds.innerHTML = 59
It changes minutes to negative value and sets seconds to 59. You should add some validation before this and don't start clock.
setInterval doesn't guarantee that your function will execute in the precise interval, just that it wouldn't execute earlier. This way, on a slow/loaded computer, the function could be called after the interval is already elapsed.
In other words, you probably wish to check if the timer has already elapsed, not if it's just about to do so.
(Number(minutes.innerHTML) <= 0 && Number(seconds.innerHTML) <= 0)

Calculating FPS Javascript ( Do I make something wrong? )

Here is my calculateFPS function:
function calculateFPS() {
const now = performance.now();
if(fps.lastCalledTime !== null) {
fps.counter++;
const secondsPerFrame = (now - fps.lastCalledTime) / 1000;
fps.totalFPS += 1 / secondsPerFrame;
if(fps.counter === 20) {
fps.fps = Math.round(fps.totalFPS / fps.counter);
fps.totalFPS = 0;
fps.counter = 0;
}
}
fps.lastCalledTime = now;
}
Here is my fps global object:
let fps = {
lastCalledTime: null,
fps: 60,
counter: 0,
totalFPS: 0,
}
However when I see my game slowing down (the game is Mowing (if I translated it correctly)) basically the FPS should go down... instead it goes up to 400 - 500
Am I doing something wrong?
Thank you in advance...
You can calculate the time difference between two function calls as such
let then = performance.now();
let now;
let frame = 1000/60;
function animate() {
now = performance.now();
console.log(((now - then)/frame) * 60);
then = now;
}
setInterval(animate, frame);

Setting the exact duration for incrementing to another number

I've made a function that increments one number to another. This works however my duration logic doesn't.
The third parameter in .animateNumber() is duration, and I want this to be the time spent incrementing to the new number.
So if the duration is set at 500, then it'll take 500 milliseconds incrementing from 10 (start) - 500 (end), and this would be the same for any starting and ending values.
http://jsbin.com/kepibetuxu/edit?html,js,console,output
HTMLElement.prototype.animateNumber = function(start, end, duration) {
this.style.opacity = 0.5;
var self = this,
range = end - start,
current = start,
increment = end > start? 1 : -1,
stepTime = duration / range,
timer = setInterval(function() {
current += increment;
self.innerHTML = current;
if (current == end) {
self.style.opacity = 1;
clearInterval(timer);
}
}, stepTime);
console.log(range);
console.log(stepTime);
};
document.querySelector('.value').animateNumber(400, 500, 20);
document.querySelector('.value1').animateNumber(0, 500, 20);
<div class="value">0</div>
<div class="value1">0</div>
How may I fix my stepTime logic to do this?
I would just request new frames, and update the number to the approapriate fraction at that time:
function animateNumber(el, start, end, duration) {
var startTime = performance.now();
(function func() {
var factor = Math.min(1, (performance.now() - startTime) / duration);
el.textContent = (start + (end-start) * factor).toFixed(0);
el.style.opacity = factor;
if(factor < 1) requestAnimationFrame(func);
})();
}
animateNumber(document.querySelector('.value1'), 400, 500, 2000);
animateNumber(document.querySelector('.value2'), 0, 500, 2000);
<div class="value1">0</div>
<div class="value2">0</div>
this is how you can do it adding a refresh rate to your function:
HTMLElement.prototype.animateNumber = function(start, end, duration, refresh) {
this.style.opacity = 0.5;
var self = this,
refresh = refresh || 10,
range = end - start,
stepQt = duration/refresh | 0,
increment = end > start? range/stepQt : -range/stepQt ,
current = start;
var timer = setInterval(function() {
current += increment ;
if (current >= end) {
current = end;
self.style.opacity = 1;
clearInterval(timer);
}
self.innerHTML = current | 0;
}, refresh);
};
document.querySelector('.value').animateNumber(400, 500, 2000);
document.querySelector('.value1').animateNumber(0, 500, 2000, 10);
<div class="value">0</div>
<div class="value1">0</div>
the refresh rate is there to help you understand that the increment should be based on the quantity of steps in the animation instead of adjusting the refresh rate based on one by one increments;
you can also not add a refresh rate as I've added a default value;

Categories

Resources