1

I have built a Pomodoro clock in ReactJS, which involves using setInterval (or setTimeout), but when I ran it to follow the sessions/breaks patterns, I discovered that at the end of a 90 minutes session on my mobile clock, there was a lag of about 30 minutes in my Pomodoro app. That is, there was a delay of 30 minutes!!

After a little research I discovered that setInterval doesn't fire exactly at the specific interval you specify, and it tends to drift depending on the CPU load, and these drifts accumulate to make a significant delay in the long run.

A solution to this problem that I came across is the self-adjusting timers that use Date.now() method to account for the drift in the recursive setTimeout function.

However, in the process, I came to also know that when a tab in Google chrome is inactive, then even the self-adjusting timers drift because of the way processes are scheduled in the CPU (inactive tabs probably means nothing important to process).

For me this poses a problem, because I would like to run my Pomodoro clock in one tab, and go and work on another tab.

My question is: how can I solve this inactive browser's tab problem to create an accurate timer in JavaScript?

BlackMath
  • 932
  • 7
  • 24
  • 1
    It sounds as if you are incrementing/decrementing the timer on every interval. Can't you store the current time when you start the timer and then calculate how much has been passed using that earlier stored timer? – Ivar Sep 16 '20 at 10:32
  • This is how self-adjusting timers work, which works fine if the tab in which the app is running is active, but this is of no use when the tab is inactive, because this will drift, too. – BlackMath Sep 16 '20 at 10:34
  • You have to store your current time first, and then you have to detect the tab browser active or not, [may you can read this reference](https://stackoverflow.com/questions/19519535/detect-if-browser-tab-is-active-or-user-has-switched-away/19519701) – Gilang Pratama Sep 16 '20 at 10:37
  • What exactly will drift? The interval or the displayed time? If you use an initially stored date object to calculate how much time has passed, it should always display the correct time, unless the actual time of the device is off, but you can't really do anything about that. – Ivar Sep 16 '20 at 10:37
  • @Ivar What drifts is the time at which the callback function is executed. For example, if you specify the interval to be 1000ms, then the callback functions will be called at 1003ms, then 1005ms after that, ... not exactly at 1000ms increment, and these drifts accumulate with time. – BlackMath Sep 16 '20 at 10:44
  • As I understand it, your goal is to display a clock on the page and you use the interval/callback to achieve that, right? If so, then does it really matter if the callback has shifted? As long as you correctly calculate how much time has passed, you can still display the correct time regardless. A bit like this: https://jsfiddle.net/r4fqhb36/ As long as the page is not refreshed, it should be accurate, even when the interval drifts. – Ivar Sep 16 '20 at 10:50
  • But if the page is inactive it drifts, too. See this https://youtu.be/x8PBWobv6NY?t=2228 – BlackMath Sep 16 '20 at 11:26
  • @BlackMath The code at that timestamp only uses the time to detect the the drift of the interval, not a drift of the time itself. (It actually uses the time to detect this in the first place.) If you use store the start date and then compare it to the current date in the callback (like in my example), then you always have an accurate time, because you are not relying on the interval itself (you are only using the setInterval to update it on the screen). The example in my previous comment should work just fine. – Ivar Sep 16 '20 at 11:45
  • Does this answer your question? [How to create an accurate timer in javascript?](https://stackoverflow.com/questions/29971898/how-to-create-an-accurate-timer-in-javascript) – Ivar Sep 16 '20 at 11:47
  • @Ivar I see. Could you please explain why your code runs every second, although the `setInterval` function fires at 200ms? Specififcally, why this part `Math.floor((new Date().getTime() - startTime.getTime()) / 1000)` results in 1 second increment? – BlackMath Sep 16 '20 at 21:01
  • 1
    @BlackMath `new Date().getTime() - startTime.getTime()` gives you the amount of milliseconds between `startTime` and the current time. By dividing it by 1000 you get the amount of seconds (1000 milliseconds = 1 second) with decimal places. Using `Math.floor()` removes all the decimal places so you only get the amount of seconds. – Ivar Sep 16 '20 at 21:05
  • Right. Thanks for explaining. Very helpful inputs. Now one last question: what I have is a count down timer (this is how the Pomodoro clock works). How can I subtract 1 from the counter every 1 second using your method? I can post my React code if that adds anything to what I am trying to accomplish, because in React I am using hooks, specifically `useEffect` and `useState` to update the count down timers. – BlackMath Sep 16 '20 at 21:17
  • 1
    @BlackMath No, subtracting one makes you rely on the accuracy of of the interval/timeout. If you want to have a count down timer, you should subtract the starttime from the current time instead of the other way around. https://jsfiddle.net/qmht7fw9/ – Ivar Sep 17 '20 at 07:16
  • Great. That worked perfectly fine. Thanks a lot. – BlackMath Sep 17 '20 at 10:20
  • @Ivar I used your code to implement the count down timers in ReactJS, but ran into the problem of the frequency at which the `useEffect` is updating the state variable https://stackoverflow.com/questions/63936170/problem-with-updating-the-state-in-useeffect-when-dependencies-are-included – BlackMath Sep 17 '20 at 23:37
  • I tested this against another online clock, and although I started my app clock 1 second earlier than the online clock, after 15 minutes my app clock was 2 seconds behind the online clock, which means that even in this method the time drifts!! I wonder if I need to decrease the interval. – BlackMath Sep 18 '20 at 10:21
  • @BlackMath I'm not familiar with react so I can't really help you in that regard. But in your question you still seem to add to the time on every iteration rather than calculating passed time and _set_ it. So if the timeout is skipping, you are still missing one addition whereas when you calculate the passed time and set it, you wouldn't have that problem. Again, I don't know React or `setClockTime` so maybe I got it wrong, but that might be the source of your problem. – Ivar Sep 18 '20 at 10:51
  • @Ivar Basically, I am adding because I have a pre-defined counter from which I need to subtract 1 each second. See here in vanilla JS https://jsfiddle.net/4f3dgu1x/ – BlackMath Sep 18 '20 at 11:25
  • 1
    @Ivar I implemented the count down counter using Vanilla JavaScript, and it worked perfectly fine. The problem is then in my React code. Until I figure it out, I will re-write my app in Vanilla JavaScript. Thanks for the insights and help :) – BlackMath Sep 18 '20 at 22:54

0 Answers0