One way to make a timer is:
let seconds = 0;
setInterval(() => {
console.log(seconds);
seconds++;
}, 1000);
and, for a rough idea of elapsed time, over a short-ish period that would be fine…ish. The problem is that running code like this in anything other than a Real Time Operating System (RTOS) is going to lead to inaccuracies.
The solution is straightforward, switch from thinking about a timer as an active thing whirring away, and instead think of it as simply a point marked in time. A logical timer if you like.
All platforms have the ability to tell you what time it is right now to within a few milliseconds. So when we want to start a timer we simply record the start datetime, and when we want it to end it, we record that datetime as well, we then compare the difference between the two points in time.
In JavaScript you can do this very simply with the MomentJS library:
let start = 0;
let end = 0;
onStart() {
start = moment();
}
onStop() {
stop = moment();
console.log(stop.diff(start, 'milliseconds'));
}
This separates your UI code from you timer code as this is now basically a headless timer, taking up very little resource and not subject to whims of the CPU as to exactly when it gets run.
You can leave this running for days if needs be and so long as start still exists in memory you will get an accurate measurement of the elapsed time.
What about a count down timer? In this instance we will need to either do some polling or rely on a single interrupt. I prefer the polling approach as I can then use the same loop to update my UI with the timer status. So, how would we do that?
const duration = 100000; // 10 seconds in ms
let timerExpiration = null;
let interval = null;
let displayedTime = 0;
onStart() {
timerExpiration = moment().add(duration, 'milliseconds');
startPolling();
}
startPolling() {
interval = setInterval(() => {
if (timerExpiration.isBefore(moment())) {
console.log('Time Up!');
timerExpiration = null;
clearInterval(interval);
}
// update UI
displayedTime = moment().diff(timerExpiration, 'milliseconds')
}, 10); // this will poll every 10 ms, increase for less accuracy, decrease for more
}
This will only be as accurate as your interval polling period. However, as you are simply comparing two points in time (now and the expiration) your error scope resets each loop, rather than accumulating like when decrementing a duration by 1 each time. The timer will always expire at the same time, how often you poll it just determines how soon after it expired you notice and can update your UI.
We now have an approach that is much better than a counter however, it still suffers from the problem of living in memory only. Refreshing the page, closing the app or turning off the device will loose the timer.
Since we are just looking at fixed points in time and comparing them to now() this is easy to solve.
We can use whatever local storage is available to save the relevant data required to “rebuild” the timer.
So using the running timer example:
// timer component code
let start = 0;
let end = 0;
onStart() {
start = moment();
LocalStorage.write({ 'activeTimer': start.toJSON() });
}
onStop() {
stop = moment();
LocalStorage.remove('activeTimer');
console.log(stop.diff(start, 'milliseconds');
}
...
...
...
// app launch code
if (LocalStorage.get('activeTimer') !== null) {
start = JSON.parse(LocalStorage.get('activeTimer'));
}
LocalStorage in this case could be a database, local plist or even a text file.
Now we have a timer that: