Skip to content

Instantly share code, notes, and snippets.

@shellcatt
Last active February 26, 2023 11:52
Show Gist options
  • Save shellcatt/ecc20d445a3ab7edd2d510c890e7f7b1 to your computer and use it in GitHub Desktop.
Save shellcatt/ecc20d445a3ab7edd2d510c890e7f7b1 to your computer and use it in GitHub Desktop.
Ticker
const moment = require('moment');
const util = require('util');
const pause = util.promisify((a, f) => setTimeout(f, a));
export class Ticker {
#timeout = null;
interval = 0; // ms
duration = 0; // ms
onTick = null;
onStop = null;
startAt = null;
endAt = null;
running = false;
constructor(interval, duration, { onTick, onStop }) {
this.interval = interval;
this.duration = duration;
this.onTick = onTick;
this.onStop = onStop;
this.running = false;
}
/**
* @description
* ! In case of 1000ms ticktime (every second) onTick lag due to network operations is reducing
* the total expected number of iterations (duration*ticktime) per `duration`
* thus missing some random spikes and drops that take less than a second
*/
async #loop() {
let i = 0;
while (this.running && moment() <= this.endAt) {
let now = moment();
// console.debug(`\nTicker::onTick() i=${++i}`);
await this.onTick({ interval: this.interval, time: now });
// If working without duration, update endAt to always be ahead
if (!this.duration) {
this.endAt = this.endAt.add(this.interval/1000 + 5, 'seconds');
}
await this.#wait(this.interval);
}
await this.stop()
}
async #wait(time) {
let p = new Promise((resolve, reject) => {
// Clears any current waiters, prevents from starting multiple untrackable async threads
clearTimeout(this.#timeout);
this.#timeout = setTimeout(() => {
resolve(true);
}, time);
});
return p;
}
async start() {
this.startAt = moment();
if (this.duration) {
this.endAt = this.startAt.add(this.duration/1000, 'seconds')
} else {
// Add a small buffer, to be updated on every tick
this.endAt = moment().add(this.interval/1000 + 60, 'seconds');
}
this.running = true;
await this.#loop();
}
async stop() {
this.running = false;
if (this.onStop/* && moment() >= this.endAt*/) {
global['emitter'].emit(`${this.namespace}:onStop`, { time: moment() });
await this.onStop({ interval: this.interval, duration: this.duration, time: moment() });
}
return this;
}
async pause() {
this.running = false;
}
async restart() {
await this.stop();
clearTimeout(this.#timeout);
await pause(50)
await this.start();
}
static async sleep(timeout) {
return pause(timeout);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment