type Untap = ( ) => void ; type UntapFromSource = ( ) => void ; interface IProps < V > { tapToSource : ( cb : ( payload : V ) => void ) => UntapFromSource ; } type Listener < V > = ( ( v : V ) => void ) | ( ( ) => void ) ; export default class Tappable < V > { private _props : IProps < V > ; private _tappers : Map < number , { bivarianceHack ( v ) : void ; } [ 'bivarianceHack' ] > ; private _untapFromSource : null | UntapFromSource ; private _lastTapperId : number ; private _untapFromSourceTimeout : null | NodeJS . Timer = null ; constructor ( props ) { this . _lastTapperId = 0 ; this . _untapFromSource = null ; this . _props = props ; this . _tappers = new Map ( ) ; } private _check ( ) { if ( this . _untapFromSource ) { if ( this . _tappers . size === 0 ) { this . _scheduleToUntapFromSource ( ) ; } } else { if ( this . _tappers . size !== 0 ) { this . _untapFromSource = this . _props . tapToSource ( this . _cb ) ; } } } private _scheduleToUntapFromSource ( ) { if ( this . _untapFromSourceTimeout !== null ) return ; this . _untapFromSourceTimeout = setTimeout ( ( ) => { this . _untapFromSourceTimeout = null ; if ( this . _tappers . size === 0 ) { this . _untapFromSource ! ( ) ; this . _untapFromSource = null ; } } , 0 ) ; } private _cb : any = ( arg ) : void => { this . _tappers . forEach ( ( cb ) => { cb ( arg ) ; } ) ; } ; tap ( cb ) { const tapperId = this . _lastTapperId ++ ; this . _tappers . set ( tapperId , cb ) ; this . _check ( ) ; return ( ) => { this . _removeTapperById ( tapperId ) ; } ; } private _removeTapperById ( id ) { this . _tappers . delete ( id ) ; this . _check ( ) ; } }