import { PromiseFunctions } from "../promise_functions" ; import { isUndefined } from "../utility" ; /**
 * `PromiseMap` is a key-value store where the values are `Promise`s.
 * It allows for inspection of the state of owned `Promise`s - whether pending,
 * fulfilled, or rejected - as well as the ability to resolve or reject owned
 * `Promise`s that are pending.
 */ export class PromiseMap < K , V > { private promises : Map < K , Promise < V > > ; private data : Map < K , MapData < V > > ; private readonly timeout ? : number ; param timeout returns public constructor ( timeout ? ) { this . promises = new Map < K , Promise < V > > ( ) ; this . data = new Map < K , MapData < V > > ( ) ; this . timeout = timeout ; } param key returns public has ( key ) { return this . promises . has ( key ) ; } param key returns public isPending ( key ) { const entry = this . data . get ( key ) ; return ! isUndefined ( entry ) && entry . status === State . Pending ; } param key returns public isFulfilled ( key ) { const entry = this . data . get ( key ) ; return ! isUndefined ( entry ) && entry . status === State . Fulfilled ; } param key returns public isRejected ( key ) { const entry = this . data . get ( key ) ; return ! isUndefined ( entry ) && entry . status === State . Rejected ; } param key param value returns public resolve ( key , value ) { return this . settle ( { key , onExisting : ( data ) => this . resolveExisting ( { key , data , value } ) , onNew : ( ) => this . resolveNew ( key , value ) , } ) ; } param key param reason returns public reject ( key , reason ? ) { return this . settle ( { key , onExisting : ( data ) => this . rejectExisting ( { key , data , reason } ) , onNew : ( ) => this . rejectNew ( reason ) , } ) ; } param reason returns public rejectAll ( reason ? ) { let numRejected = 0 ; for ( const [ key , data ] of this . data . entries ( ) ) { if ( data . status !== State . Pending || isUndefined ( data . functions ) ) { continue ; } this . reject ( key , reason ) ; ++ numRejected ; } return numRejected ; } param key returns public async get ( key ) { const maybePromise = this . promises . get ( key ) ; if ( ! isUndefined ( maybePromise ) ) { return maybePromise ; } const promise = new Promise < V > ( ( resolve , reject ) => this . data . set ( key , { functions : { resolve , reject } , status : State . Pending , } ) ) ; this . promises . set ( key , promise ) ; return promise ; } param key returns public delete ( key ) { return this . doDelete ( key , new Error ( "deleted" ) ) ; } returns public clear ( ) { const error = new Error ( "cleared" ) ; for ( const controlBlock of this . data . values ( ) ) { if ( isUndefined ( controlBlock . functions ) ) { continue ; } controlBlock . functions . reject ( error ) ; } const numDeleted = this . promises . size ; this . promises . clear ( ) ; this . data . clear ( ) ; return numDeleted ; } param key param onExisting param onNew returns private settle ( { key , onExisting , onNew } ) { const maybeData = this . data . get ( key ) ; const hasKey = ! isUndefined ( maybeData ) ; if ( hasKey ) { const data = maybeData ! ; const newData = onExisting ( data ) ; this . data . set ( key , newData ) ; } else { const [ promise , data ] = onNew ( ) ; this . promises . set ( key , promise ) ; this . data . set ( key , data ) ; } this . setTimeout ( key ) ; return ! hasKey ; } param key param data param value private resolveExisting ( { key , data , value } ) { if ( ! isUndefined ( data . functions ) ) { data . functions . resolve ( this . doResolve ( key , value ) ) ; } else { this . promises . set ( key , this . doResolve ( key , value ) ) ; } return { ... removeFunctions ( data ) , status : State . Pending , } ; } param key param value returns private resolveNew ( key , value ) { return [ this . doResolve ( key , value ) , { status : State . Pending } ] ; } param key param data param reason private rejectExisting ( { key , data , reason } ) { if ( ! isUndefined ( data . functions ) ) { data . functions . reject ( reason ) ; } else { this . promises . set ( key , Promise . reject ( reason ) ) ; } return { ... removeFunctions ( data ) , status : State . Rejected , } ; } param reason returns private rejectNew ( reason ? ) { return [ Promise . reject ( reason ) , { status : State . Rejected } ] ; } private async doResolve ( key , value ) { try { const resolved = await value ; this . setStatus ( key , State . Fulfilled ) ; return resolved ; } catch ( error ) { this . setStatus ( key , State . Rejected ) ; throw error ; } } param key private setStatus ( key , status ) { const maybeExisting = this . data . get ( key ) ; const toSet = ( ( ) => { if ( typeof maybeExisting === "undefined" ) { return { status } ; } return { ... maybeExisting , status , } ; } ) ( ) ; this . data . set ( key , toSet ) ; } param key returns private setTimeout ( key ) { const maybeData = this . data . get ( key ) ; if ( isUndefined ( maybeData ) || isUndefined ( this . timeout ) ) { return false ; } if ( ! isUndefined ( maybeData . timer ) ) { clearTimeout ( maybeData . timer ) ; } const doDelete = ( ) => this . doDelete ( key , new Error ( "timed out" ) ) ; const withTimer = { ... maybeData , timer : setTimeout ( doDelete , this . timeout ) , } ; this . data . set ( key , withTimer ) ; return true ; } param key param reason returns private doDelete ( key , reason ) { const maybeData = this . data . get ( key ) ; if ( ! isUndefined ( maybeData ) ) { if ( ! isUndefined ( maybeData . functions ) ) { maybeData . functions . reject ( reason ) ; } if ( ! isUndefined ( maybeData . timer ) ) { clearTimeout ( maybeData . timer ) ; } } this . data . delete ( key ) ; return this . promises . delete ( key ) ; } } /**
 * Potential states for a Promise to be in. `Fulfilled` and `Rejected` are both
 * settled states.
 */ enum State { Pending , Fulfilled , Rejected } /**
 * Control block data for a `PromiseMap` entry.
 */ interface MapData < T > { readonly functions ? : PromiseFunctions < T > ; readonly status : State ; readonly timer ? : NodeJS . Timer ; } param data returns const removeFunctions = < T > ( data ) : MapData < T > => { const { functions : _ , ... toReturn } = data ; return toReturn ; } ;