interface Waiter { ID: number; resolve: (payload: TPayload) => void; reject: (error: Error) => void; // eslint-disable-next-line timer?: any; resolved: boolean; timedout: boolean; matcher: TMatcher; } type Validator = (payload: TPayload, matcher: TMatcher) => boolean; type TimeoutFormatter = (matcher: TMatcher, timeout: number) => string; class Waitress { private waiters: Map>; private validator: Validator; private timeoutFormatter: TimeoutFormatter; private currentID: number; public constructor(validator: Validator, timeoutFormatter: TimeoutFormatter) { this.waiters = new Map(); this.timeoutFormatter = timeoutFormatter; this.validator = validator; this.currentID = 0; } public resolve(payload: TPayload): void { for (const entry of this.waiters.entries()) { const index = entry[0]; const waiter = entry[1]; if (waiter.timedout) { this.waiters.delete(index); } else if (this.validator(payload, waiter.matcher)) { clearTimeout(waiter.timer); waiter.resolved = true; waiter.resolve(payload); this.waiters.delete(index); } } } public remove(ID: number): void { const waiter = this.waiters.get(ID); if (waiter) { if (!waiter.timedout && waiter.timer) { clearTimeout(waiter.timer); } this.waiters.delete(ID); } } public waitFor( matcher: TMatcher, timeout: number ): {ID: number; start: () => {promise: Promise; ID: number}} { const ID = this.currentID++; const promise: Promise = new Promise((resolve, reject): void => { const object: Waiter = {matcher, resolve, reject, timedout: false, resolved: false, ID}; this.waiters.set(ID, object); }); const start = (): {promise: Promise; ID: number} => { const waiter = this.waiters.get(ID); if (waiter && !waiter.resolved && !waiter.timer) { waiter.timer = setTimeout((): void => { const message = this.timeoutFormatter(matcher, timeout); waiter.timedout = true; waiter.reject(new Error(message)); }, timeout); } return {promise, ID}; }; return {ID, start}; } } export default Waitress;