import { Handler } from "./types/Handler"; import { Evt } from "./Evt"; import { Polyfill as Set } from "minimal-polyfills/dist/lib/Set"; import { Polyfill as WeakMap } from "minimal-polyfills/dist/lib/WeakMap"; import { getLazyEvtFactory } from "./util/getLazyEvtFactory"; import { UnpackEvt } from "./types/helper/UnpackEvt"; type EvtCore = import("./EvtCore").EvtCore; type Done = [Error | null, T, Handler.WithEvt[]]; export class Ctx { /** Posted each time ctx.done() is invoked, post the detached handler ( return value of evt.done()) */ public readonly getEvtDone: () => Evt>; /** * Return a promise that resolve next time ctx.done(result) is invoked * Reject if ctx.abort(error) is invoked. * Optionally a timeout can be passed, if so the returned promise will reject * with EvtError.Timeout if done(result) is not called * within [timeout]ms. * If the timeout is reached ctx.abort(timeoutError) will be invoked. */ public getPrDone(timeout?: number): Promise { return this.getEvtDone() .waitFor(timeout) .then( ([error, result]) => { if (!!error) { throw error; } return result; }, timeoutError => { this.abort(timeoutError); throw timeoutError; } ) ; } /** Posted every time a handler is bound to this context */ public readonly getEvtAttach: () => Evt>; /** Posted every time a handler bound to this context is detached from it's Evt */ public readonly getEvtDetach: () => Evt>; private readonly onDone: ([error, result, handlers]: Done) => void; private readonly onAttach: (handler: UnpackEvt>) => void; private readonly onDetach: (handler: UnpackEvt>) => void; constructor() { { const { getEvt, post } = getLazyEvtFactory>(); this.onDone = post; this.getEvtDone = getEvt; } { const { getEvt, post } = getLazyEvtFactory< UnpackEvt> >(); this.getEvtAttach = getEvt; this.onAttach = post; } { const { getEvt, post } = getLazyEvtFactory< UnpackEvt> >(); this.getEvtDetach = getEvt; this.onDetach = post; } } /** * All the handler will be detached. * evtDone will post [ error, undefined, handlers (detached) ] * if getPrDone() was invoked the promise will reject with the error */ public abort(error: Error) { return this.__done(error); } /** * Detach all handlers. * evtDone will post [ null, result, handlers (detached) ] * If getPrDone() was invoked the promise will result with result */ public done(result: T) { return this.__done(undefined, result); } /** Detach all handler bound to this context from theirs respective Evt and post getEvtDone() */ private __done(error: Error | undefined, result?: T): Handler.WithEvt[] { const handlers: Handler.WithEvt[] = []; for (const handler of this.handlers.values()) { const evt = this.evtByHandler.get(handler)!; const wasStillAttached = handler.detach(); //NOTE: It should not be possible if (!wasStillAttached) { continue; } handlers.push({ handler, evt }); } this.onDone([ error ?? null, result as NonNullable, handlers ]); return handlers; } private handlers = new Set< Handler >(); private evtByHandler = new WeakMap< Handler, EvtCore >(); public getHandlers(): Handler.WithEvt[] { return Array.from(this.handlers.values()) .map(handler => ({ handler, "evt": this.evtByHandler.get(handler)! })) ; } public static __addHandlerToCtxCore( handler: Handler>, evt: EvtCore ) { const { ctx } = handler; ctx.handlers.add(handler); ctx.evtByHandler.set(handler, evt); ctx.onAttach({ handler, evt }); } public static __removeHandlerFromCtxCore( handler: Handler ) { const { ctx } = handler; ctx.onDetach({ handler, "evt": ctx.evtByHandler.get(handler)! }); ctx.handlers.delete(handler); } public static __matchHandlerBoundToCtx( handler: Handler ): handler is Handler { return handler.ctx !== undefined; } } export class VoidCtx extends Ctx { /** * Detach all handlers. * evtDone will post [ null, undefined, handlers (detached) ] * If getPrDone() was invoked the promise will resolve */ public done(): ReturnType { return super.done(undefined); } }