setTimeout calling the function prematurely - javascript

My library has test cases based on real time, and I noticed that tests will randomly fail with 1 millisecond error:
expect(received).toBeGreaterThanOrEqual(expected)
Expected: >= 1000
Received: 999
This seems to be due to setTimeout calling the function prematurely.
So I wrote a separate test script:
let last = Date.now()
setTimeout(next, 1000)
function next() {
if (Date.now() - last < 1000) process.exit(1)
last = Date.now()
setTimeout(next, 1000)
}
On Node.js v12.19.0, v14.15.3, v15.4.0, it will fail randomly: sometimes the script can continue to run, sometimes the script will exit soon.
This is not only happening on my local computer, but also on Github's CI server.
My questions: Is this a bug? Or some kind of expected behavior of setTimeout? Or Date.now() - time always needs to add 1 millisecond?
UPDATE: See also https://github.com/nodejs/node/issues/26578

Update: using git-bisect here is the culprit:
2c409a285359faae58227da283a4c7e5cd9a2f0c is the first bad commit
commit 2c409a285359faae58227da283a4c7e5cd9a2f0c
Date: Tue Aug 25 13:36:37 2020 -0600
perf_hooks: add idleTime and event loop util
Use uv_metrics_idle_time() to return a high resolution millisecond timer
of the amount of time the event loop has been idle since it was
initialized.
Include performance.eventLoopUtilization() API to handle the math of
calculating the idle and active times. This has been added to prevent
accidental miscalculations of the event loop utilization. Such as not
taking into consideration offsetting nodeTiming.loopStart or timing
differences when being called from a Worker thread.
PR-URL: https://github.com/nodejs/node/pull/34938
This seems like a bug, not an expected behavior. I would vote against always adding 1ms since the behavior is inconsistent. (However, will it ever be earlier more than 1 ms? I didn't observe more than 1ms) You may workaround the problem with the following:
const origSetTimeout = setTimeout;
setTimeout = (f, ms, ...args) => {
let o;
const when = Date.now() + ms,
check = ()=> {
let t = when - Date.now();
if (t > 0) Object.assign(o, origSetTimeout(check, t));
else f(...args);
};
return o = origSetTimeout(check, ms);
};
It will allow to clearTimeout() even while working around the problem.
Here is a browser code that simulates the problem and alternates the workaround every 3 seconds:
// Simulate the problem
const realOrigSetTimeout = setTimeout;
setTimeout = (func, ms, ...args) => realOrigSetTimeout(func, ms - Math.random(), ...args);
const ms = 200;
let when = Date.now() + ms;
setTimeout(next, ms);
function next() {
let now = Date.now();
setTimeout(next, ms);
console.log(now < when ? 'premature' : 'ok');
when = now + ms;
}
function workAround() {
console.log('Applying workaround');
const origSetTimeout = setTimeout;
setTimeout = (f, ms, ...args) => {
let o;
const when = Date.now() + ms,
check = ()=> {
let t = when - Date.now();
if (t > 0) Object.assign(o, origSetTimeout(check, t));
else f(...args);
};
return o = origSetTimeout(check, ms);
};
setTimeout(_=>{
console.log('Removing workaround');
setTimeout = origSetTimeout;
setTimeout(workAround, 3000);
}, 3000);
}
setTimeout(workAround, 3000);
Below is a nodejs code that will clearly show the problem ('p' among dots) and will apply the workaround after pressing enter.
'use strict';
const ms = 1;
let when = Date.now() + ms;
setTimeout(next, ms);
function next() {
let now = Date.now();
setTimeout(next, ms);
process.stdout.write(now < when ? 'p' : '.');
when = now + ms;
}
process.stdin.on('readable', _=> {
console.log('enabling workaround');
const origSetTimeout = setTimeout;
setTimeout = (f, ms, ...args) => {
let o;
const when = Date.now() + ms,
check = ()=> {
let t = when - Date.now();
if (t > 0) Object.assign(o, origSetTimeout(check, t));
else f(...args);
};
return o = origSetTimeout(check, ms);
};
});

Related

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.

How to start a second timer after the first timer expires in Vuex?

I have a Vue project, and in the Vuex store I have these states:
state: {
gameStartTimer: 5,
counter: false,
randomNumber: Number,
clickAlert: false
}
Now, in the actions, I have the following:
actions: {
async startCounter({ state }) {
state.counter = true;
state.clickAlert = false;
while (state.gameStartTimer > 0 && state.counter) {
// this sets the timer to count down from 5 to 0
await new Promise(resolve => setTimeout(resolve, 1000));
if (state.counter)
state.gameStartTimer--;
// the if-statement ensures nowTime is acquired upon gameStartTimer reaches 0
if (state.gameStartTimer == 0) {
let timeNow = new Date().getTime();
state.nowTime = timeNow;
}
}
state.counter = false;
// I want to start a second timer here which counts down every second
// until the randomNumber state reaches 0
await new Promise(resolve => setTimeout(resolve, 1000));
if (state.clickAlert)
state.randomNumber--;
if (state.randomNumber == 0) {
state.clickAlert = true;
}
}
},
}
The problem I am facing is that the first timer is enveloped in a while-loop, which is what I want, so that the game starts counting down from 5 to 0.
Then, I want a second timer (randomNumber is used for the duration) to run in the background, then turn the clickAlert state to true.
However, I could not get the second timer to run at all inside an async/await method. I am not quite sure what the syntax or logic problem is.
Any tip is appreciated.
The obvious solution seems to be to just wrap the second timer in a while loop too.
while (state.randomNumber > 0) {
await new Promise(resolve => setTimeout(resolve, 1000));
state.randomNumber--;
if (state.randomNumber === 0) {
state.clickAlert = true;
}
}
async/await is just a way to avoid callback functions. It's functionally equivalent to this:
while (state.randomNumber > 0) {
setTimeout(() => {
state.randomNumber--;
}, 1000);
}

Building a React clock with hooks

I'm working on building a clock that counts up, just a practice exercise (I know there are better ways of doing this).
My issue is when a minute is added, the "addMinute" seems to run twice, and I can't for the life of me figure out how or why.
Here is a demo on codesandbox: https://codesandbox.io/s/restless-frost-bud7p
And here is the code at a glance:
(please note, the counter only counts up to 3 and counts faster; this is just for testing purposes)
const Clock = (props) => {
const [seconds, setSeconds] = useState(0)
const [minutes, setMinutes] = useState(0)
const [hours, setHours] = useState(0)
const addHour = () => {
setHours(p => p + 1)
}
const addMinute = () => {
setMinutes(prev => {
if (prev === 3) {
addHour()
return 0
} else {
return prev + 1
}
})
}
const addSecond = () => {
setSeconds(prevState => {
if (prevState === 3) {
addMinute()
return 0
} else {
return prevState + 1
}
})
}
useEffect(() => {
const timer = setInterval(addSecond, 600)
return () => clearInterval(timer)
}, [])
return (
<div>
<h1>time!</h1>
<p>
{hours < 10 ? 0 : ""}{hours}
:
{minutes < 10 ? 0 : ""}{minutes}
:
{seconds < 10 ? 0 : ""}{seconds}
</p>
<p>
{seconds.toString()}
</p>
</div>
)
}
The issue is that you are using the React.StrictMode wrapper in the index.js file.
Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects
So you should decide between using strict mode or having side effects, the easy way is just removing the React.StrictMode wrapper. The other way is removing side effects, where you only need to do the following:
Update your addSecond and addMinute functions to something like:
const addMinute = () => {
setMinutes((prev) => prev + 1);
};
const addSecond = () => {
setSeconds((prevState) => prevState + 1);
};
And your useEffect call to something like:
useEffect(() => {
if(seconds === 3) {
addMinute();
setSeconds(0);
};
if(minutes === 3) {
addHour();
setMinutes(0);
}
const timer = setInterval(addSecond, 600);
return () => clearInterval(timer);
}, [seconds, minutes]);
Here an updated version of your code: https://codesandbox.io/s/goofy-lake-1i9xf
A couple of issues,
first you need to use prev for minutes, so
const addMinute = () => {
setMinutes(prev => {
if (prev === 3) {
addHour()
return 0
} else {
return prev + 1
}
})
}
And then you need to remove the React.StrictMode wrapper component from index, which is what is actually causing the double increase, as part of what the strict mode does is
This is done by intentionally double-invoking the following functions:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer
see: https://codesandbox.io/s/pensive-wildflower-pujmk
So I had no idea about strict mode and the intentional double renders. After reading the documentation I finally understand the purpose of this.
As such, it appears the best solution is to have no side effects from the useEffect, and instead, handle that logic outside of the effect, but still changing every second.
So, I set an effect that has a piece of state starting at zero and going up by one per second.
Then, with each change of "time", the useMemo will recalculate how many hours, mins and seconds the total time is.
The only thing I don't like is all those calculations running every render! (But realistically those take but a few miliseconds, so performance doesn't seem to be an issue).
const [time, setTime] = useState(0)
useEffect(() => {
const timer = setInterval(() => {
setTime(p => p + 1)
}, 999);
return () => clearTimeout(timer);
}, [userState]);
const timeJSON = useMemo(() => {
const hrs = Math.floor(time/3600)
const mins = Math.floor( (time-(3600*hrs)) / 60 )
const secs = time - (3600 * hrs) - (60*mins)
return {
hrs,
mins,
secs,
}
}, [time])
return (
<div>
<p>
{timeJSON.hrs < 10 ? 0 : ""}{timeJSON.hrs}
:
{timeJSON.mins < 10 ? 0 : ""}{timeJSON.mins}
:
{timeJSON.secs < 10 ? 0 : ""}{timeJSON.secs}
</p>
</div>
)
Thanks again for everyone pointing me in the right direction on this!

Why setInterval does not work correactly?

I am trying to make a stopwatch (00:00:00:00).But my one sec is slower than real one sec.
I also changed the value of setInterval 10 to 1 but nothing changed. When I changed it as 100, it worked, time flowed slower.
(00:00:00:00)=(hh:mm:ss:ms)
Here is a part of my code:
const [time, setTime] = useState({
ms: 0,
ss: 0,
mm: 0,
hh: 0
})
let degisenMs = time.ms,
degisenH = time.hh,
degisenM = time.mm,
degisenS = time.ss;
const run = () => {
if (updatedMs === 100) {
updatedS++;
updatedMs = 0
}
if (updatedS === 60) {
updatedM++;
updatedS = 0;
}
if (M === 60) {
updatedH++;
updatedM = 0
}
updatedMs++;
return (setTime({
ms: updatedMs,
ss: updatedS,
mm: updatedM,
hh: updatedH
}))
}
const start = () => {
setStatus(1)
run()
setInterv(setInterval(run, 10))
}
The problem is that setInterval is not exact, it is approximate. One option is to use web workers to increase accuracy as described in the link, but it is still not exact.
When it comes to measuring time, it is better to track the start timestamp and figure out how much time has passed at each tick/update. You can then update the UI or trigger an alarm etc. Here is some pseudocode.
const [ startTime, setStartTime ] = useState(null)
const [ intervalId, setIntervalId ] = useState(null)
function tick() {
const now = new Date()
const elapsedMs = now - startTime
// Update UI etc using elapsedMs
}
function start() {
setStartTime(new Date())
// Run tick() every 100ms
setIntervalId(setInterval(tick, 100))
}
function stop() {
clearInterval(intervalId)
}

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

Categories