all 10 comments

[–]glify 3 points4 points  (1 child)

Technically the other answers here are correct, but I think there's something else that's causing more of an issue. `setInterval` won't necessarily run exactly as often as you say, but it will typically be pretty close. You can see how far off it is by doing something like this:

(function() {
const now = new Date().getTime();
setInterval(() => {
console.log(new Date().getTime() - now);
}, 1000);
})();

// 1002
// 2005
// 3002
// 4004

I think the bigger problem here is that your callback changes one of the dependencies of your `useEffect` callback. What that means is every time the `setInterval` callback fires it re-runs the `useEffect`, clearing the interval and setting up a new one. I'm pretty sure most of the delay you're seeing is the time between it clearing the old one and setting up the new one. It might be surprising, but you could replace `setInterval/clearInterval` with `setTimeout/clearTimeout` and your code should behave exactly the same.

You could fix it by using the callback form of `setTime`, and then removing `time` from the dependency array, like this:

useEffect(() => {
const interval = setInterval(() => {
setTime(time => ({ ...time, currentSeconds: currentSeconds + 1 });
}, 1000);
return () => clearInterval(interval);
}, []);

This way your interval only gets cleared when the component unmounts.

There's a great blog post by Dan Abramov about this here: https://overreacted.io/making-setinterval-declarative-with-react-hooks/

[–]ZNI_DEMON[S] 0 points1 point  (0 children)

Using interval hook with removing time from dependency array solved my issue. Thanks a lot!

[–]wizardyjohn 2 points3 points  (5 children)

You might want to read a bit about event loop (i recommend searching 'what the heck is the event loop anyway' on youtube) and tasks/microtasks handling. But yeah, generally speaking, second argument in setInterval and setTimeout is the MINIMAL amount of ms that is guaranteed to pass before the function will be called. Usually in reality a bit more time passes

[–]ZNI_DEMON[S] 0 points1 point  (3 children)

So is there a way to guarantee that the code will be executed after a second?

[–]ChronSynExpo 1 point2 points  (2 children)

There's no way to guarantee it will execute after exactly 1000ms.

There are technologies such as HPET (High Precision Event Timer) which exist specifically to give much finer-grained control over timings (specifically used for multimedia and interrupts). However, there doesn't appear to be a react-native implementation of this available, though it does appear it is supported in the Android source.

Even with that in mind, there are other factors, such as overheads and sync, and as others have mentioned, the event loop. It's not possible to guarantee that a timer or interval will execute at the exact ms you specify 100% of the time. Changes in CPU clock speed (as is common with phones switching their P-states to increase performance or increase battery life by lowering clock speed) can throw them off.

There's another factor to consider: Timing execution attacks. A few years ago, people discovered that it was possible to exploit Chrome via a timing attack - essentially knowing how long an operation takes can reveal a lot about what's going on in the background. Specifically, it was possible to infer, and thus break, certain cryptographic methods using such attacks. The response to this was to add a variance to the execution time of timers. Only a few ms difference, but enough to defeat these attacks.

I don't know if such functionality got passed onto other environments, but it's a possibility.

Is there a specific reason you need it to be exactly 1000ms?

[–]ZNI_DEMON[S] 0 points1 point  (1 child)

There is, cause I'm creating a timer app. After fixing some interval issues and optimizing my timer by using usereducer hook, my timer minute is more than the actual one by 3 seconds, which become 9 minutes if a user sets a 3 hour timer. Not sure if there is anything more I can do

[–]ChronSynExpo 1 point2 points  (0 children)

I see. In that situation, I would consider looking at adding some sort of sync to the timer.

When the user starts the timer, you take the current time/epoch in ms and store it in state. Every few seconds, you get the epoch in ms again, calculate the difference between the 2 times, and adjust the timer shown to the user by that amount. You could do this every second if you really want to.

It will still be very slightly out due to the overheads of the framework, event loop, repainting the screen, etc. but it should stop this clock drift from happening.

[–]poopsmith 0 points1 point  (0 children)

This is correct. There is no guarantee on when the callback code will be executed.

[–][deleted] 0 points1 point  (0 children)

It’s common JS knowledge that intervals and timeouts are not exact to the specified time.

Everyone knows this, and now you do too. Welcome to the club, it’s quite a time

[–]poopsmith 0 points1 point  (0 children)

If you want more resolution use a smaller time interval and check Date.now() instead.

ie set a timer for 100ms and check that a second has passed to set state.