import { EventEmitter } from 'tsee'; const DEFAULT_POLL_INTERVAL = 300; const defaultIsEqual = (a: any, b: any) => a === b; type WatcherEvents = { change: (newValue: T, oldValue?: T) => void; dispose: () => void; }; export class Watcher extends EventEmitter> { constructor(propListenerDisposer?: () => void) { super(); this._propListenerDisposer = propListenerDisposer; } private _propListenerDisposer?: () => void; get propListenerDisposer() { return this._propListenerDisposer; } lastValue: T|undefined; readonly emitChange = (newValue: T) => { const oldValue = this.lastValue; this.lastValue = newValue; this.emit('change', newValue, oldValue); }; readonly setPropListenerDisposer = (disposer: Watcher["_propListenerDisposer"]) => { this.disposePropListener(); this._propListenerDisposer = disposer; }; /** unbind all listeners from this watcher and bind to other */ readonly rebindTo = (otherWatcher: Watcher) => { const l = this.rawListeners('change'); otherWatcher.rawListeners('change').push( ...l, ); l.splice(0, l.length); }; /** unbind all listeners from this watcher and bind to other and disposeEmitter */ readonly rebindDisposePropListener = (otherWatcher: Watcher) => { this.rebindTo(otherWatcher); this.disposePropListener(); }; readonly disposePropListener = () => { if (this.propListenerDisposer) { this.propListenerDisposer(); this._propListenerDisposer = undefined; } }; } export function watchProperty( getter: () => Value, opts: { pollInterval?: number, isEqual?: (oldV: Value, newV: Value) => boolean, watcher?: Watcher, /** true by default */ emitOnStart?: boolean, } = {} ): Watcher { const { pollInterval = DEFAULT_POLL_INTERVAL, isEqual = defaultIsEqual, watcher = new Watcher(), } = opts; let { emitOnStart = true, } = opts; let oldVal = getter(); const frame = () => { const newVal = getter(); if (emitOnStart) { watcher.emitChange(newVal); emitOnStart = false; } if (!isEqual(oldVal, newVal)) { watcher.emitChange(newVal); oldVal = newVal; } }; const intervalId = setInterval(frame, pollInterval); watcher.setPropListenerDisposer(() => { clearInterval(intervalId); }); return watcher; } export function watchCascadePair( parent: Watcher, child: (newValue: A, oldWatcher?: Watcher) => Watcher, ) { let watcherB = new Watcher(); parent.on('change', newVal => { watcherB = child(newVal, watcherB); }); watcherB.on('dispose', () => { parent.emit('dispose'); }); return watcherB; } export function watchCascade( parent: Watcher, child: (newValue: A, oldWatcher?: Watcher) => Watcher, ) { const b = watchCascadePair(parent, child); const next = (then: (newValue?: B, oldWatcher?: Watcher) => Watcher) => { watchCascade(b, then); }; Object.assign(next, { last: b }); return next as (typeof next & { last: typeof b }); }