type RejectType = { type: 'reject'; reason: string | Error; }; type ResolveType = { type: 'resolve'; value?: T; }; type Response = ResolveType | RejectType | { type: 'timeout' | 'abort'; }; const enum TimeoutState { notYetWait = 0, waiting = 1, done = 2, timeout = 3, }'' /** * 재사용 가능한 Signal 클래스 */ export class Signal { private resolved: boolean = false; private timeoutState: TimeoutState = TimeoutState.notYetWait; private _promise!: Promise>; private _resolve!: (value: Response) => void; private timeoutID?: ReturnType; constructor(public readonly timeoutMs?: number) { this.reset(); } public reset(): void { if(this.timeoutID){ clearTimeout(this.timeoutID); this.timeoutID = undefined; } this.resolved = false; this.timeoutState = TimeoutState.notYetWait; this._promise = new Promise((resolve) => { this._resolve = resolve; }); if(this.timeoutMs){ this.timeoutID = setTimeout(() => { this._timeout(); }, this.timeoutMs); } } private _timeout(){ if(this.timeoutState === TimeoutState.waiting){ this.resolved = true; this._resolve({type: 'timeout'}); } this.timeoutState = TimeoutState.timeout; } public async wait(): Promise>{ if(this.timeoutState === TimeoutState.timeout){ this.reset(); return {type: 'timeout'}; } this.timeoutState = TimeoutState.waiting; const result = await this._promise; this.reset(); return result; } public abort(): void { if(this.resolved){ return; } this.resolved = true; this.timeoutState = TimeoutState.done; if(this.timeoutID){ clearTimeout(this.timeoutID); this.timeoutID = undefined; } this._resolve({type: 'abort'}); } public notify(value?: T): void { if(this.resolved){ return; } this.resolved = true; this.timeoutState = TimeoutState.done; if(this.timeoutID){ clearTimeout(this.timeoutID); this.timeoutID = undefined; } this._resolve({type: 'resolve', value}); } }