Remove Tappable
and Emitter
This commit is contained in:
parent
391958f5cf
commit
867cf51acb
4 changed files with 1 additions and 188 deletions
|
@ -254,7 +254,7 @@ class PrismInstance<V> implements Prism<V> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns a tappable that fires every time the prism's state goes from `fresh-\>stale.`
|
||||
* Calls `callback` every time the prism's state goes from `fresh-\>stale.` Returns an `unsubscribe()` function.
|
||||
*/
|
||||
onStale(callback: () => void): VoidFn {
|
||||
const untap = () => {
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
import Emitter from './Emitter'
|
||||
|
||||
describe('DataVerse.Emitter', () => {
|
||||
it('should work', () => {
|
||||
const e: Emitter<string> = new Emitter()
|
||||
e.emit('no one will see this')
|
||||
e.emit('nor this')
|
||||
|
||||
const tappedEvents: string[] = []
|
||||
const untap = e.tappable.tap((payload) => {
|
||||
tappedEvents.push(payload)
|
||||
})
|
||||
e.emit('foo')
|
||||
e.emit('bar')
|
||||
untap()
|
||||
e.emit('baz')
|
||||
expect(tappedEvents).toMatchObject(['foo', 'bar'])
|
||||
})
|
||||
})
|
|
@ -1,76 +0,0 @@
|
|||
import Tappable from './Tappable'
|
||||
|
||||
type Tapper<V> = (v: V) => void
|
||||
type Untap = () => void
|
||||
|
||||
/**
|
||||
* An event emitter. Emit events that others can tap (subscribe to).
|
||||
*/
|
||||
export default class Emitter<V> {
|
||||
private _tappers: Map<any, (v: V) => void>
|
||||
private _lastTapperId: number
|
||||
private _onNumberOfTappersChangeListener: undefined | ((n: number) => void)
|
||||
|
||||
/**
|
||||
* The Tappable associated with this emitter. You can use this to tap (subscribe to) events emitted.
|
||||
*/
|
||||
readonly tappable: Tappable<V>
|
||||
|
||||
constructor() {
|
||||
this._lastTapperId = 0
|
||||
this._tappers = new Map()
|
||||
this.tappable = new Tappable({
|
||||
tapToSource: (cb: Tapper<V>) => this._tap(cb),
|
||||
})
|
||||
}
|
||||
|
||||
_tap(cb: Tapper<V>): Untap {
|
||||
const tapperId = this._lastTapperId++
|
||||
this._tappers.set(tapperId, cb)
|
||||
this._onNumberOfTappersChangeListener?.(this._tappers.size)
|
||||
return () => {
|
||||
this._removeTapperById(tapperId)
|
||||
}
|
||||
}
|
||||
|
||||
_removeTapperById(id: number) {
|
||||
const oldSize = this._tappers.size
|
||||
this._tappers.delete(id)
|
||||
const newSize = this._tappers.size
|
||||
if (oldSize !== newSize) {
|
||||
this._onNumberOfTappersChangeListener?.(newSize)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a value.
|
||||
*
|
||||
* @param payload - The value to be emitted.
|
||||
*/
|
||||
emit(payload: V) {
|
||||
for (const cb of this._tappers.values()) {
|
||||
cb(payload)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the emitter has tappers (subscribers).
|
||||
*/
|
||||
hasTappers() {
|
||||
return this._tappers.size !== 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls callback when the number of tappers (subscribers) changes.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* emitter.onNumberOfTappersChange((n) => {
|
||||
* console.log("number of tappers changed:", n)
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
onNumberOfTappersChange(cb: (n: number) => void) {
|
||||
this._onNumberOfTappersChangeListener = cb
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
type Untap = () => void
|
||||
type UntapFromSource = () => void
|
||||
|
||||
interface IProps<V> {
|
||||
tapToSource: (cb: (payload: V) => void) => UntapFromSource
|
||||
}
|
||||
|
||||
type Listener<V> = ((v: V) => void) | (() => void)
|
||||
|
||||
/**
|
||||
* Represents a data-source that can be tapped (subscribed to).
|
||||
*/
|
||||
export default class Tappable<V> {
|
||||
private _props: IProps<V>
|
||||
private _tappers: Map<number, {bivarianceHack(v: V): void}['bivarianceHack']>
|
||||
private _untapFromSource: null | UntapFromSource
|
||||
private _lastTapperId: number
|
||||
private _untapFromSourceTimeout: null | NodeJS.Timer = null
|
||||
|
||||
constructor(props: IProps<V>) {
|
||||
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()
|
||||
/*
|
||||
* this._untapFromSource()
|
||||
* this._untapFromSource = null
|
||||
*/
|
||||
}
|
||||
} 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: any): void => {
|
||||
for (const cb of this._tappers.values()) {
|
||||
cb(arg)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tap (subscribe to) the data source.
|
||||
*
|
||||
* @param cb - The callback to be called on a change.
|
||||
*/
|
||||
tap(cb: Listener<V>): Untap {
|
||||
const tapperId = this._lastTapperId++
|
||||
this._tappers.set(tapperId, cb)
|
||||
this._check()
|
||||
return () => {
|
||||
this._removeTapperById(tapperId)
|
||||
}
|
||||
}
|
||||
|
||||
private _removeTapperById(id: number) {
|
||||
this._tappers.delete(id)
|
||||
this._check()
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @deprecated
|
||||
// */
|
||||
// map<T>(transform: {bivarianceHack(v: V): T}['bivarianceHack']): Tappable<T> {
|
||||
// return new Tappable({
|
||||
// tapToSource: (cb: (v: T) => void) => {
|
||||
// return this.tap((v: $IntentionalAny) => {
|
||||
// return cb(transform(v))
|
||||
// })
|
||||
// },
|
||||
// })
|
||||
// }
|
||||
}
|
Loading…
Reference in a new issue