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);
Related
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.
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)
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.
I need to build a stream that makes api request immediately after the call and that each hour (1:00 PM, 2:00 PM) if page wasn't refreshed. I build this with setTimeout() but I want to implement it with RxJs. Can you help me pls.
isUpdated() {
return new Observable<Object>(function update(obs) {
this.http.get(`/update`).subscribe(data => {
obs.next(data);
});
const timer = (60 - new Date().getMinutes()) * 60 * 1000;
setTimeout(() => update.call(this, obs), timer);
}.bind(this));
}
//call
isUpdated().subscribe(data => console.log(data));
I think that to solve this problem, you need to break it down into smaller pieces.
First of all, we know that at some point, based on the current time, we'll want to know when to trigger the next call. If we get a timestamp which gives us the current time in ms and we want to get the number of ms before the next hour, here's how we can do it:
const timeToNextHourInMs = (currentTimestampMs) => {
const timestampSeconds = currentTimestampMs / 1000;
const numberOfSecondsIntoTheCurrentHour = timestampSeconds % 3600;
const numberOfSecondsToTheNextHour = 3600 - numberOfSecondsIntoTheCurrentHour;
return numberOfSecondsToTheNextHour * 1000;
};
I hope that the variable names are explicit enough that I do not need to comment but let me know otherwise.
Next, we want to tackle the stream issue:
We want to trigger an HTTP call straight away
Get the emitted value straight away
Do all the above again every time a new hour start (1:00, 2:00, 3:00, etc..)
Here's how you can do this:
this.http.get(`/update`).pipe(
timestamp(),
switchMap(({ timestamp, value }) =>
concat(
of(value),
EMPTY.pipe(delay(timeToNextHourInMs(timestamp)))
)
),
repeat()
);
Let's go through the above logic:
First, we make the HTTP call straight away
Once the HTTP call is done, we get the current timestamp (to later on based on that find out when we want to do the next call)
We do a switchMap but as our HTTP call is only ever going to return 1 value it doesn't really matter in this very specific case. We could use flatMap or concatMap too
Inside the switchMap, we use concat to first of all send the value that we just got from the HTTP call but also keep that observable alive until the end of the current our (by using the function we created earlier)
At the end of the current hour, the stream will therefore complete. BUT, as we've got a retry, as soon as the stream completes, we'll subscribe to it again (and as a reminder, the stream will only complete at the very beginning of a new hour!)
One thing I'd suggest to add here but which isn't a requirement of the initial issue would be to have some error handling so that if something goes wrong when you make that call, it automatically retries it a few seconds after. Otherwise imagine when the polling kicks in if your network doesn't work for 5s at that exact time your stream will error straight away.
For this, you can refer to this brilliant answer and do that in a reusable custom operator:
const RETRY_DELAY = 2000;
const MAX_RETRY_FOR_ONE_HTTP_CALL = 3;
const automaticRetry = () => (obs$) =>
obs$.pipe(
retryWhen((error$) =>
error$.pipe(
concatMap((error, index) =>
iif(
() => index >= MAX_RETRY_FOR_ONE_HTTP_CALL,
throwError(error),
of(error).pipe(delay(RETRY_DELAY))
)
)
)
)
);
This will retry the observable 3 times with a delay between each retry. After 3 times, the stream will error by throwing the last emitted error.
Now, we can just add this custom operator to our stream:
this.http.get(`/update`).pipe(
automaticRetry(),
timestamp(),
switchMap(({ timestamp, value }) =>
concat(
of(value),
EMPTY.pipe(delay(timeToNextHourInMs(timestamp)))
)
),
repeat()
);
I haven't actually tested the code above so please do that on your side and let me know how it goes. But if my logic is correct here's how things should go:
Imagine you start your app at 2:40
An HTTP call is made straight away
You get the response pretty much instantly
The stream is set to be kept open for 20mn
At 3:00, the stream is completed and the retry kicks in: We do another HTTP call
This time, the server got re-deployed and was not available for a few seconds
Internally, the stream errors but thanks to our custom operator automaticRetry it waits for 3 seconds then retries the HTTP call once, still nothing. It waits another 3 seconds and this time it's fine, the result is passed downstream
Repeat this indefinitely :)
Let me know how it goes
I think it is very simple.
Timer accepts two parameters, the first is the delay for the first call and then the interval in which the call should be made. You got the next full hour offset right, I'm using your code for that.
Use startWith(null) to start immediately.
const startDelayMs = (60 - new Date().getMinutes()) * 60 * 1000;
const hourMs = 60 * 60 * 1000;
timer(startDelayMs, hourMs).pipe(
startWith(null),
switchMap(()) // do your call here
)
Maybe you could do it like this (I didn't test this code):
import { merge, concat, of, timer, interval } from 'rxjs';
import { switchMap } from 'rxjs/operators';
...
isUpdated() {
return concat(
merge(
of(null), // make call inmediately
timer((60 - new Date().getMinutes()) * 60 * 1000), // the next full hour eg. 2:00
),
interval(60 * 60 * 1000), // every hour
).pipe(
switchMap(() => this.http.get(...)),
)
})
concat will subscribe to interval only after the first merge completes which is at the next full hour eg.: 2:00 and then emit every hour.
#MrkSef had a good idea that this could be simplified to something like this:
isUpdated() {
return timer(
(60 - new Date().getMinutes()) * 60 * 1000, // the next full hour eg. 2:00
60 * 60 * 1000 // every hour
).pipe(
startWith(null), // initial request
switchMap(() => this.http.get(...)),
)
})
You can simply use rxjs interval
import { interval } from 'rxjs';
const seconds = 3600;
const source = interval(seconds * 1000);
source.subscribe(()=> {
// make api request here
});
Ok, so you want to:
create two streams: first one will always tick every hour (60 000 ms), second - will control the allowance of emitting these hourly ticks
const every60Minutes$ = interval(60 * 1000);
const isAllowedToProceedSubject = new Subject<boolean>(); // subject is also an Observable stream
combine those two streams and only allow to emit the values if the second stream has "true" as the latest value
const every60MinsIfAllowed$ = combineLatest([
isAllowedToProceedSubject,
every60Minutes$
]).pipe(
filter(([isAllowed, intervalTick]) => {
return isAllowed;
}),
flatMap(() => {
return of(new Date()); // replace with HTTP call
})
)
be able to control the second stream
setTimeout(() => {
isAllowedToProceedSubject.next(true); // this allows the values to be emitted
}, 2500);
// or add some click handler
Working example (but with emitting every 2000ms for better clarity)
In my code, I'm trying to put a certain delay before continuing to the rest of the code. Pretty basic. I'm using a custom sleep function because javascript's native sleep function is not working at all. I'm actually working in app script in google spreadsheets so maybe that's why. But the following code is in the <script> tag of the html file in spreadsheet app script.
Anyway, when I use sleep(), it is being executed before the setTimeout
function get_ids(db){
window.alert("before window.ids is saved?");
google.script.run.withSuccessHandler(getIdsFromAppscript).getIdsFromModel(db);
//this returns a result to getIdsFromAppscript but the following code doesn't wait
//for the result to be returned so I want to use sleep before the ids
//variable is returned by get_ids
setTimeout(function(){
window.alert("checking if ids is saved after 10s?");
window.alert("In timeout ids="+window.ids);
var ids= window.ids; //is non empty
},10000);
sleep(10000);
var ids= window.ids;
window.alert("after wait");
window.alert("after sleep ids="+ids); //is empty but should be non empty
return ids; //=window.ids , Need to return a non-empty result
}
function getIdsFromAppscript(result){
window.ids=result;
}
and the sleep function:
function sleep(ms) {
var start = new Date().getTime(), expire = start + ms;
while (new Date().getTime() < expire) { }
return;
}
Current Order of output based on window.alert():
1) before window is saved?
2) after sleep
3) after sleep ids= //basically empty which shouldn't be the case
4) checking if ids is saved after 10s?
5) In timeout ids= [.....] //non empty list
However, my desired output order is:
1) before window is saved?
2) checking if ids is saved after 10s?
3) In timeout ids= [.....] //non empty list
4) after sleep
5) after sleep ids= [...] //should be a non empty list
The reason why I'm writing setTimeout is to show that after 10 seconds, the result is being stored in window.ids however even after I give a sleep for 10 seconds, or even 30 seconds, I can't get the result from window.ids.
What exactly am I doing wrong here? Thanks in advance~
Java Script, especially through the V8 engine does not sleep. Sleeping causes the entire thread that JavaScript runs on to stop, which breaks the whole point of asynchronocy. setTimeout() only waits to run the function you push into it for the time you also put into it. It doesn't stop the rest of the executions, and whatever happens first will happen first.
If it's important to you that something happens in order, always, then you need to use callbacks or promises.
An example in code could be:
function doTimeout(ms) {
setTimeout(function(){
window.alert("checking if ids is saved after 10s?");
window.alert("In timeout ids="+window.ids);
var ids= window.ids; //is non empty
},ms);
}
function sleep(ms, callback) {
var start = new Date().getTime(), expire = start + ms;
while (new Date().getTime() < expire) { }
callback(ms);
}
sleep(10000, doTimeout);
Javascript is single threaded. You must return from your code for scripts in other threads to execute. Script in other threads includes functions to handle a timeout event, functions called when promises are kept or fail, and call back functions provided for asynchronous requests made using an XMLHttpRequest object.
Writing a function and calling it sleep() does not change this. You could have called it waitingForGodot() for all the difference it would make. What the code you provided does is to spend a lot of time looping in the thread it was called in. It does not relinquish control and blocks all other scripts from executing. If it goes on for long enough my browser will ask me if I wish to abort the (as in your) script.
I have included two examples below showing that your sleep function blocks the entire Javascript engine. When I use your sleep function, the interval function does not get executed even though I have set an interval of 100 ms and the output is delayed by 10 seconds. However, in the second example the output does get printed immediately at the correct interval. This shows your sleep function is blocking the entire execution engine and that explains why your ids array is empty.
function sleep(ms) {
var start = new Date().getTime(),
expire = start + ms;
while (new Date().getTime() < expire) {}
return;
}
function get_ids() {
document.write("before window.ids is saved?" + "<br>");
var counter = 0;
setInterval(function() {
while (counter < 100) {
document.write("checking if ids is saved after 10s?" + "<br>");
counter = counter + 1
}
}, 100);
sleep(10000);
documen.write("after wait");
}
document.write("Start");
get_ids()
document.write("End");
In this example I have commented out your sleep function and as expected the output gets printed every 100 ms:
function sleep(ms) {
var start = new Date().getTime(),
expire = start + ms;
while (new Date().getTime() < expire) {}
return;
}
function get_ids() {
document.write("before window.ids is saved?" + "<br>");
var counter = 0;
setInterval(function() {
while (counter < 100) {
document.write("checking if ids is saved after 10s?" + "<br>");
counter = counter + 1
}
}, 100);
//sleep(10000);
documen.write("after wait");
}
document.write("Start");
get_ids()
document.write("End");