I have some asynchronous problems. I\'m working on an ECMAScript 6 object. It\'s a timer and I want to be able to restart during its countdown.
Here is my work:
An easier way to know if the timer is "running" is to perhaps use setInterval
instead.
var interval = setInterval(() => updateTimer(), 10); // update every 10ms
It's running if interval
is set
if (interval) // timer is running
Stop the timer
window.clearInterval(interval);
interval = null;
// timer is no longer "running"
Additional notes
Beware of creating timers that increment with a fixed value
In your code, you have
setTimeout(() => this.count--, 1000);
The intention is for you to decrement your count
property once every second, but this is not the behavior you will be guaranteed.
Check out this little script
var state = {now: Date.now()};
function delta(now) {
let delta = now - state.now;
state.now = now;
return delta;
}
setInterval(() => console.log(delta(Date.now())), 1000);
// Output
1002
1000
1004
1002
1002
1001
1002
1000
We used setInterval(fn, 1000)
but the actual interval varies a couple milliseconds each time.
The problem is exaggerated if you do things like switch your browser's focus to a different tab, open a new tab, etc. Look at these more sporadic numbers
1005 // close to 1000 ms
1005 // ...
1004 // a little variance here
1004 // ...
1834 // switched focus to previous browser tab
1231 // let timer tab run in background for a couple seconds
1082 // ...
1330 // ...
1240 // ...
2014 // switched back to timer tab
1044 // switched to previous tab
2461 // rapidly switched to many tabs below
1998 // ...
2000 // look at these numbers...
1992 // not even close to the 1000 ms that we set for the interval
2021 // ...
1989 // switched back to this tab
1040 // ...
1003 // numbers appear to stabilize while this tab is in focus
1004 // ...
1005 // ...
So, this means you can't rely upon your setTimeout
(or setInterval
) function getting run once per 1000
ms. count
will be decremented with much variance depending on a wide variety of factors.
To work around this, you need to use a delta. That means before each "tick" of your timer, you need to take a timestamp using Date.now
. On the next tick, take a new timestamp and subtract your previous timestamp from the new one. That is your delta
. Using this value, add it to the Timer's total ms
to get the precise number of milliseconds the timer has been running for.
Then, all time-sensitive values will be a projection/calculation of the total accumulated ms.
In your case, say you have a count
that starts at 10
. If you want to count down by -1
each 1000
ms, you could do
function update() {
// update totalMs
this.totalMs += calculateDelta();
// display count based on totalMS
console.log("count %d", Math.ceil(this.count - this.totalMs/1000));
}
Here's a sample ES6 timer that implements a delta
function that might help you
class Timer {
constructor(resolution=1000, ms=0) {
this.ms = ms
this.resolution = resolution;
this.interval = null;
}
delta(now) {
let delta = now - this.now;
this.now = now;
return delta;
}
start() {
this.now = Date.now();
this.interval = window.setInterval(() => this.update(), this.resolution);
}
reset() {
this.update();
this.ms = 0;
}
stop() {
this.update();
window.clearInterval(this.interval);
this.interval = null;
}
update() {
this.ms += this.delta(Date.now());
console.log("%d ms - %0.2f sec", this.ms, this.ms/1000);
}
}
Create a new timer with a 50
ms "resolution". All this means is that the timer display is updated every 50 ms. You could set this value to anything and the timer will still keep an accurate value.
var t = new Timer(50);
t.start();
To simulate the reset, we can just create a one-off timeout so you can see the reset working
// in ~5 seconds, reset the timer once
setTimeout(() => t.reset(), 5000);
Here's a demonstration of pausing the timer
// in ~10 seconds, pause the timer
setTimeout(() => t.stop(), 10000);
And you can resume the timer, too
// in ~12 seconds, resume the timer (without reset)
setTimeout(() => t.start(), 12000);
You can start
, stop
, reset
the timer as much as you like
Here's an the ES6 (above) transpiled to ES5 so you can see the code working in a runnable snippet. Open your console and click Run code snippet.
"use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var Timer = (function () {
function Timer() {
var resolution = arguments.length <= 0 || arguments[0] === undefined ? 1000 : arguments[0];
var ms = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1];
_classCallCheck(this, Timer);
this.ms = ms;
this.resolution = resolution;
this.interval = null;
}
Timer.prototype.delta = function delta(now) {
var delta = now - this.now;
this.now = now;
return delta;
};
Timer.prototype.start = function start() {
var _this = this;
this.now = Date.now();
this.interval = window.setInterval(function () {
return _this.update();
}, this.resolution);
};
Timer.prototype.reset = function reset() {
this.update();
this.ms = 0;
};
Timer.prototype.stop = function stop() {
this.update();
window.clearInterval(this.interval);
this.interval = null;
};
Timer.prototype.update = function update() {
this.ms += this.delta(Date.now());
console.log("%d ms - %0.2f sec", this.ms, this.ms / 1000);
};
return Timer;
})();
var t = new Timer(50);
t.start();
// in ~5 seconds, reset the timer once
setTimeout(function () {
return t.reset();
}, 5000);
// in ~10 seconds, pause the timer
setTimeout(function () {
return t.stop();
}, 10000);
// in ~12 seconds, resume the timer (without reset)
setTimeout(function () {
return t.start();
}, 12000);