///
import { EventEmitter } from 'events';
export class Timer extends EventEmitter {
protected running: Promise = null;
protected started: boolean = false;
private ticking = false;
protected time: number;
protected timer: number = 0;
constructor(protected cb: () => T) {
super();
this.reset = this.reset.bind(this);
this.tick = this.tick.bind(this);
this.delayNext = this.delayNext.bind(this);
this.handleError = this.handleError.bind(this);
}
immediately() {
if (this.running) {
return this.running.then(() => {
return this.immediately();
}, () => {
return this.immediately();
});
}
const started = this.started;
if (started) {
this.reset();
}
const p = this.tick();
if (started) {
p.then(() => {
if (!this.started) {
this.started = true;
this.delayNext();
}
});
}
return p;
}
interval(ms: number) {
this.time = ms;
}
/* resolve or reject first tick, after the 1st, no promise anymore */
start(ms?: number): Promise {
if (this.started) {
throw new Error('Cannot start timer when already started');
}
this.started = true;
if (ms >= 0) {
this.interval(ms);
}
return this.delay();
}
stop() {
if (this.started) {
this.reset();
}
}
protected delay(): Promise {
if (!this.time) {
throw new TypeError('no timer value set.');
}
return new Promise((resolve, reject) => {
// this.emit('start');
this.timer = setTimeout(() => {
const p = this.tick();
p.then(resolve, reject);
p.catch(this.reset);
p.catch(this.handleError);
p.then(this.delayNext);
}, this.time);
});
}
protected delayNext() {
if (!this.started) {
return;
}
this.timer = setTimeout(() => {
const p = this.tick();
p.catch(this.handleError);
p.then(this.delayNext);
}, this.time);
}
private handleError(e: Error) {
if (this.listenerCount('error') > 0) {
try {
this.emit('error', e);
} catch (e) {
console.error('\x1B[0;38;5;9m%s\x1B[0m', e.stack);
process.exit(1);
}
} else {
console.error('\x1B[0;38;5;9m%s\x1B[0m', e.stack);
process.exit(1);
}
}
private reset() {
if (this.timer) {
clearTimeout(this.timer);
}
this.started = false;
this.timer = 0;
// this.emit('end');
}
protected async tick() {
if (this.ticking) {
throw new Error('try to run multiple tick function.');
}
if (this.running) {
console.error('tick function call when running. ignored.');
return this.running;
}
this.ticking = true;
this.running = Promise.resolve(this.cb());
this.ticking = false;
// this.emit('tick');
const ret = await this.running;
this.running = null;
return ret;
}
get isRunning() {
return !!this.running;
}
}
export type Timeout = Promise&{kill(asSuccess?: boolean): void};
export function timeout(ms: number): Timeout {
let to = null;
let kill: any;
const p = new Promise((resolve, reject) => {
to = setTimeout(() => {
resolve();
}, ms);
kill = (asSuccess: boolean = false) => {
clearTimeout(to);
if (asSuccess) {
resolve();
} else {
reject();
}
};
});
p.kill = kill;
return p;
}