Count down with setInterval has time difference [duplicate] - javascript

This question already has answers here:
What is the reason JavaScript setTimeout is so inaccurate?
(5 answers)
Closed 1 year ago.
let count = 60;
function timeLeft(){
count = count - 1;
if(count == 0){
count = 60;
}
}
setInterval(() => timeLeft(), 1000)
setInterval(() => machoAirline.changePrice(), 60000);
setInterval(() => yenoTech.changePrice(), 60000);
This code is the countdown for showing how much time is left until stocks' prices change. But after some minutes, there is a gap between counter and prices update.
console:
price updated!
counter: 42
What's the problem?

Timeout and intervals may take longer to fire when you want due to:
inactive of tabs
throttling of tracking scripts
Late timeouts because browser busy with other tasks.
If you need correct order of machoAirline and yenoTech execution you can combine them in one setInterval:
setInterval(() => {
machoAirline.changePrice();
yenoTech.changePrice();
}, 60000);

setInterval does not keep accurate time, as described in other answers. This means that calculating elapsed time by counting the invocations of setInterval will result in inaccurate timings when compared to wall-time (or indeed, other intervals that you have set).
performance.now() is the most accurate measure of time available in the browser.
If timing is critical, then consider polling performance.now() in the body of a setInterval callback that runs at a short interval...
Something like:
const now = performance.now();
const finishTime = now + 3000; // 3s time
const handle = setInterval(() => {
if (performance.now() >= finishTime) {
console.log("some action");
clearInterval(handle); // kill the timer
}
}, 100); // poll every 100ms (ish)

Related

setTimeout callbacks have different execution orders in Firefox and Chrome

When I run this code in Firefox and Chrome, the results are different:
function run() {
setTimeout(() => console.log("1"), 0);
setTimeout(() => console.log("2"), 100);
let start = Date.now();
while (Date.now() - start < 200) {
// do nothing
}
setTimeout(() => {
console.log("3");
}, 0);
start = Date.now();
while (Date.now() - start < 200) {
// do nothing
}
setTimeout(() => {
console.log("4");
}, 0);
}
run();
In Chrome (and Node.js), this is printed:
1
3
2
4
In Firefox, this is printed:
1
2
3
4
But if I remove the line 2 (setTimeout(() => console.log("1"), 0);), then the same thing is printed on every platform:
2
3
4
How to explain these different results?
Thanks!
The explanation: It doesn't matter.
The details of when deferred "messages" are added to the event loop message queue are implementation details, not documented guarantees. By the time your function yields control back to the event loop, all of your setTimeout call are eligible to execute (three of them were scheduled to run immediately, one of them was scheduled to run in 100 ms) and you've guaranteed it's been at least 400 ms since you scheduled it.
The difference between the two could be as simple as whether they choose to look for deferred tasks that have become ready (to move from the deferred queue to the main "ready to go" message queue) immediately before or immediately after new items are inserted in the main message queue. Chrome chooses to move immediately after 3 is scheduled (so 3 goes in, then the deferred 2), Firefox immediately before (moving in 2 before it puts 3 in).
Both of them could change in the next release without violating any documented guarantees. Don't rely on it, don't expect it to be stable. While immediately scheduled tasks are guaranteed to execute in FIFO order, there are no guarantees on when deferred tasks get moved onto the "ready-to-go" message queue. The spec seems to requires that 1, 3 and 4 execute in that order (since they were all immediately ready, not deferred), with only the ordering of 2 being flexible, but even that isn't a true guarantee; it can get weird with the various ways in which an "immediate" setTimeout task may not actually be scheduled immediately.
You may be interested in the MDN docs on why setTimeout can take longer than expected; it explains by side-effect a lot of how the event loop works, even as it carefully provides no guarantees on the details you're exploring.
I can't give you an full detailed explanation, but the second paramter of setTimeoput and setInterval doesn't mean, it will exactly execute it at that time. They will put it in a queue, so the background can execute it.
The browser has a lifecycle when to execute specific steps to update the data and the styles.
I can only send you this youtube link, that helped me to learn more about it:
https://www.youtube.com/watch?v=MCi6AZMkxcU
1, 2, 3, 4 is the behavior that is expected.
The specs ask to
Wait until any invocations of this algorithm that had the same global and orderingIdentifier, that started before this one, and whose milliseconds is equal to or less than this one's, have completed.
So any call to setTimeout that were both made before, and had their milliseconds set to a lower value should be called first.
Firefox, Safari, and the current stable channel of Chrome all do this.
So when the event loop gains control again, it sees that all the timers are ready to be called, and it queues tasks for each, in this scheduled called order:
"1": scheduled-time = t=0 + 0 = 0
"2": scheduled-time = t=0 + 100 = 100
"3": scheduled-time = t=200 + 0 = 300
"4": scheduled-time = t=400 + 0 = 400
But, what Chrome apparently used to do and still does in its other branches is that they only do look at the milliseconds param to do the ordering and ignore the first "that started before this one" condition.
So in there we've got,
"1": milliseconds = 0
"3": milliseconds = 0
"4": milliseconds = 0
"2": milliseconds = 100
Below is a rewrite of this logic:
// We use a MessageChannel to hook on each iteration of the event loop
function postTask(cb) {
const channel = postTask.channel ??= new MessageChannel();
const { port1, port2 } = channel;
port1.addEventListener("message", (evt) => { cb() }, { once: true });
port1.start();
port2.postMessage("");
}
const timers = new Set();
let ended = false; // So we can stop our loop after some time
function timeoutChecker() {
const now = performance.now();
const toCall = Array.from(timers)
.filter(({ startTime, millis }) => startTime + millis <= now)
.sort((a, b) => a.millis - b.millis);
while(toCall.length) {
const timer = toCall.shift();
timers.delete(timer);
timer.callback();
}
if (!ended) {
postTask(timeoutChecker);
}
}
function myTimeout(callback, millis) {
const startTime = performance.now();
timers.add({ startTime, millis, callback });
}
// Begin our loop
postTask(timeoutChecker);
// OP's code
function run() {
myTimeout(() => console.log("1"), 0);
myTimeout(() => console.log("2"), 100);
let start = Date.now();
while (Date.now() - start < 200) {
// do nothing
}
myTimeout(() => {
console.log("3");
}, 0);
start = Date.now();
while (Date.now() - start < 200) {
// do nothing
}
myTimeout(() => {
console.log("4");
}, 0);
}
run();
// all should be done after 1s
setTimeout(() => ended = true, 1000);
As for why you sometimes may see "2" before "4" in Chrome and node.js, it's because they do clamp 0ms timeout to 1ms (thought they're working on removing this in Chrome). So when the event loop gains control at t=400, this log("4") timeout may not have met the timer condition yet.
Finally about Chrome's branch thing, I must admit I'm not sure at all what happens there. Running a bisect (against Canary branch) I couldn't find a single revision where the current stable branch behavior happens, so this must be a branch settings thing.

How to make an accurate delay function in javascript

I'm making a music composer and to play the notes i'm waiting for the time (in ms) to pass before playing the next notes, i've seen that this seems farily inaccurate with sometimes up to 10ms of inaccuracy, is there a way i can make a more accurate timeout or delay function to get down to 0/1 ms of discrepancy?
My code currently is:
function delayMs(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms)
})
}
with the "tick" function:
while (this.state.isPlaying) {
const { song, settings } = this.state
let msPerBPM = Math.floor(60000 / settings.bpm.value)
await delayMs(msPerBPM)
this.handleTick()
}
i'm able to use service workers as i've noticed that i get this issue with delays from re renders in react.
It is easy enough to demonstrate that using setTimeout (and by extension setInterval) you will never get millisecond accuracy.
The below demonstrates that even telling javascript to execute in 1 millisecond it can be anywhere from a few ms to tens of ms later the call is actually made.
function recall(count){
console.log(new Date())
if(++count<10)
setTimeout(recall,1,count);
}
recall(1)
You might have more luck with requestAnimationFrame but again you'll need to calculate how long has elapsed since last step and use that to "synchronise" whatever you want to do:
let start;
function step(timestamp) {
if (start === undefined)
start = timestamp;
const elapsed = timestamp - start;
console.log(elapsed);
if (elapsed < 500) { // Stop the animation after 0.5 seconds
window.requestAnimationFrame(step);
}
}
window.requestAnimationFrame(step);

How to use promise with set interval

I’m trying to solve a simple problem here but I have no idea what direction to take.
getAuthNumber() // returns a promise with a number (eg 98765)
// response times can be 5s-20s
<div class=“auth”> </div>
//code
let counter = 0;
let el = document.getElementsbyClassName(“auth”)[0];
let func = setInterval(function(){
counter++;
getAuthNumber().then((num)=>{
return [num, counter];
}).then(res){
If(counter == res[1])
el.innerHTML = res[0];
}, 10000);
I need to write a function that gets the auth number every 10s & displays it in the block below. I’ve tried using set interval but getAuthNumber() can take more than 10s to return in which case, I need to discard that response and only show the current value.
Do not use setInterval instead use setTimeOut function.
setInterval execution depends on the CPU usage, if it in case increased the setinterval will not gets completed in the interval specified
What you can do is run an async function inside setInterval. In the following code snippet, the getAuth function completes after 2s but setInterval runs every 1s. But it still works because there is an async funciton inside setInterval.
const getAuth = () => {
return new Promise((res, rej) => {
setTimeout(() => res(Math.random()), 2000);
});
};
const setDiv = async () => {
const res = await getAuth();
console.log(res);
};
setInterval(setDiv, 1000);
I have adapted this gist by Jake Archibald (see JavaScript counters the hard way - HTTP 203) into the following code:
function promiseInterval(milliseconds, signal, promiseFactory, callback) {
const start = performance.now();
function tick(time) {
if (signal.aborted){
return;
}
promiseFactory().then(
value => {
callback(value);
scheduleTick(time);
}
);
}
function scheduleTick(time) {
const elapsed = time - start;
const roundedElapsed = Math.round(elapsed / milliseconds) * milliseconds;
const targetNext = start + roundedElapsed + milliseconds;
const delay = targetNext - performance.now();
setTimeout(tick, delay);
}
scheduleTick(start);
}
Starting from the gist, I have removed the use of requestAnimationFrame and document.timeline.currentTime (using only performance.now), and I have added the promiseFactory parameter, plus some renaming (animationInterval renamed to promiseInterval, ms renamed to milliseconds and scheduleFrame renamed to scheduleTick) and formatting.
You would use it like this:
const controller = new AbortController(); // This is used to stop
promiseInterval(
10000, // 10s
controller.signal, // signal, to stop the process call `controller.abort`
getAuthNumber, // the promise factory
num => {el.innerHTML = num;} // this is what you do with the values
);
It will not really call getAuthNumber each 10 seconds. Instead, it will wait until getAuthNumber completes and schedule to call on the next 10 seconds interval, and repeat. So it is not calling it multiple times and discarding values.

Break out of while loop with Async/Await

Context:
I have a while loop that i want to run 10 times and within i have async/awaait code that runs every 3 seconds. The while loop works as sort of a timeout if it ever runs 10 times and the async/await check doesnt return the expected value then, break out of the while loop the process timed out.
Problem: The break out of loop portion of the code is running first with the value of i(loop variable) maxed out. As i figure the way i have it setup i have no access to the value of i while is looping and only when i is at its max value.
Question: How can i escape out of this loop early when condition is met or if i is exhausted?
var i = 0;
//Run 10 Times
while (i < 10) {
//Run every 3 seconds
((i) => {
setTimeout( async () => {
isAuthenticated = await eel.is_authenticated()();
sessionStorage.sessionStatus = JSON.stringify(isAuthenticated);
console.log('------NEW STATUS-------');
console.log(JSON.parse(sessionStorage.sessionStatus).authenticated);
console.log('Inside:' + i);
}, 3000 * i)
})(i++);
//Break out of loop early if condition is met or I is exhausted, but it only runs 1 time and i is always max
if (i === 9 || JSON.parse(sessionStorage.sessionStatus).authenticated) {
console.log('Outside:' + i);
checkStatus('retried');
break;
}
}
NOTE: In case anyone is wondering eel.is_authenticated()(); is not a typo, its a python library to create desktop applications the double ()() is normal.
Also if this approach is overly complicated for what it does, any other ways to approach it are welcome :)
Thanks
The issue here is that you are running through all your loop iterations immediately (10 times), setting up 10 timeouts in the process, 3s apart from each other:
Your loop runs 10 times, creating 10 timeouts
You reach the i === 9 case
3s later, 1st timeout runs
3s later, 2nd timeout runs
...
What you want is for the loop to actually wait 3s between iterations. For that, you'd use setTimeout in a different way - you'd create a promise that resolves when the timeout is hit and await that promise:
// As helper function for readability
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
// Your loop
let i;
for (i = 0; i < 10; i++) {
// Wait 3s
await delay(3000);
// Check authentication
const isAuthenticated = await eel.is_authenticated()();
sessionStorage.sessionStatus = JSON.stringify(isAuthenticated);
console.log('------NEW STATUS-------');
console.log(JSON.parse(sessionStorage.sessionStatus).authenticated);
console.log('Inside:' + i);
// Break loop if authenticated
if (isAuthenticated.authenticated) break;
}
// We were authenticated, or looped 10 times
// Note: Because I moved this outside, i will now actually be 10 if the loop
// ended by itself, not 9.
console.log('Outside:' + i);
checkStatus('retried');
One consequence here though would be that if the call to is_authenticated takes a significant amount of time, the checks will be more than 3s apart, because we are now waiting for 3s and for this call. If this is undesired, we can reduce the delay time based on how much time elapsed since the last call:
// As helper function for readability
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
// We will save here when the last delay completed, so that the checks are always
// 3s apart (unless the check takes longer than 3s)
// Initially we save the current time so that the first wait is always 3s, as before
let lastIteration = Date.now();
// Your loop
let i;
for (i = 0; i < 10; i++) {
// Wait until 3s after last iteration (limited to 0ms, negative waits won't work)
await delay(Math.max(lastIteration + 3000 - Date.now(), 0));
// Update "last iteration" time so the next delay will wait until 3s from now again
lastIteration = Date.now();
// Check authentication
const isAuthenticated = await eel.is_authenticated()();
sessionStorage.sessionStatus = JSON.stringify(isAuthenticated);
console.log('------NEW STATUS-------');
console.log(JSON.parse(sessionStorage.sessionStatus).authenticated);
console.log('Inside:' + i);
// Break loop if authenticated
if (isAuthenticated.authenticated) break;
}
// We were authenticated, or looped 10 times
// Note: Because I moved this outside, i will now actually be 10 if the loop
// ended by itself, not 9.
console.log('Outside:' + i);
checkStatus('retried');
All of this assumes that the function in which this code is located is async. If it isn't, you need to make it async but then you need to remember adding a .catch(e => handleTheErrorSomehow(e)) when you call it, to avoid unhandled promise rejections!

setInterval different from chrome and firefox

The following code prints different result between Firefox and Chrome
var start = Date.now()
var id = setInterval(function interval() {
var whileStart = Date.now()
console.log(whileStart - start)
while (Date.now() - whileStart < 250) {
}
}, 100)
setTimeout(function timeout() {
clearInterval(id)
console.log('timeout',Date.now() - start)
}, 400)
chrome 74 print:
100
351
605
timeout 855
firefox 67 print:
101
351
timeout 601
why?
add setTimeout delay, the result is still different.
var start = Date.now()
var id = setInterval(function interval() {
var whileStart = Date.now()
console.log(whileStart - start)
while (Date.now() - whileStart < 250) {
}
}, 100)
setTimeout(function timeout() {
clearInterval(id)
console.log('timeout',Date.now() - start)
}, 500)
setTimeout queues your request to be handled at the first opportunity after the specified delay. Once the delay has elapsed and the call stack is empty your request is handled. This means there can be slight variations in the timing depending on what else the browser’s engine has going on.
That's because Chrome's implementation of setInterval does correct the drift between each call.
So instead of blindly calling again setTimeout(fn, 250) at the end of the interval's callback, it actually does setTimeout(fn, max(now - 250, 0)).
So this gives
t Firefox (no drift control) | Chrome (drift control)
––––––––––––––––––––––––––––––––––––––––––––––––––––––––
0 schedule interval #100ms schedule interval #100ms
0 schedule timeout #400ms schedule timeout #400ms
100 exec interval callback exec interval callback
=> block 250ms => block 250ms
... ...
350 schedule interval #450ms schedule interval #350ms
(now + 100ms) (now + max(100ms - 250ms, 0))
350 exec interval callback
=> block 250ms
400 exec timeout callback ...
=> cancel interval ...
...
600 schedule interval #600ms
exec interval callback
=> block 250ms
...
850 schedule interval #850ms
exec timeout callback
=> cancel interval
Note that the last interval #600 is actually dependent on which of timeout or interval as been scheduled first.
And also note that Chrome's behavior may become the standard in a near future: https://github.com/whatwg/html/issues/3151
The difference arises because of code differences between the two browsers.
If you reverse the order of starting the interval and timeout timers, Chrome behaves the same as Firefox:
"use strict";
var start = Date.now()
setTimeout(function timeout() { // start the timeout first
clearInterval(id)
console.log('timeout',Date.now() - start)
}, 400)
var id = setInterval(function interval() {
var whileStart = Date.now()
console.log(whileStart - start)
while (Date.now() - whileStart < 250) {
}
}, 100)
If you keep the same order as in the post but reduce the blocking to, say, 50 milliseconds, the interval time gets called at the 400ms mark in Chrome and blocks the timeout being called while it blocks the event loop. But in Firefox the setTimeout for 400ms gets called first, clears the interval timer and prevents the interval timer being called at all at the 400ms mark.
In short, timeout processing for intervals and timers is coded differently in the two browsers, and in case 2 above, Firefox appears to add 10ms to the interval between interval-timer calls (as allowed in Section 7.5 of HTML 5.2).

Categories