Skip to content

Commit

Permalink
feat: add "once" and "off" to the Interceptor class
Browse files Browse the repository at this point in the history
  • Loading branch information
kettanaito committed Sep 18, 2023
1 parent 45df468 commit a32813a
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 8 deletions.
30 changes: 27 additions & 3 deletions src/BatchInterceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,35 @@ export class BatchInterceptor<
public on<EventName extends ExtractEventNames<Events>>(
event: EventName,
listener: Listener<Events[EventName]>
) {
): this {
// Instead of adding a listener to the batch interceptor,
// propagate the listener to each of the individual interceptors.
this.interceptors.forEach((interceptor) => {
for (const interceptor of this.interceptors) {
interceptor.on(event, listener)
})
}

return this
}

public once<EventName extends ExtractEventNames<Events>>(
event: EventName,
listener: Listener<Events[EventName]>
): this {
for (const interceptor of this.interceptors) {
interceptor.once(event, listener)
}

return this
}

public off<EventName extends ExtractEventNames<Events>>(
event: EventName,
listener: Listener<Events[EventName]>
): this {
for (const interceptor of this.interceptors) {
interceptor.off(event, listener)
}

return this
}
}
25 changes: 25 additions & 0 deletions src/Interceptor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,31 @@ it('does not set a maximum listeners limit', () => {
expect(interceptor['emitter'].getMaxListeners()).toBe(0)
})

describe('on()', () => {
it('adds a new listener using "on()"', () => {
const interceptor = new Interceptor(symbol)
expect(interceptor['emitter'].listenerCount('event')).toBe(0)

const listener = vi.fn()
interceptor.on('event', listener)
expect(interceptor['emitter'].listenerCount('event')).toBe(1)
})
})

describe('off()', () => {
it('removes a listener using "off()"', () => {
const interceptor = new Interceptor(symbol)
expect(interceptor['emitter'].listenerCount('event')).toBe(0)

const listener = vi.fn()
interceptor.on('event', listener)
expect(interceptor['emitter'].listenerCount('event')).toBe(1)

interceptor.off('event', listener)
expect(interceptor['emitter'].listenerCount('event')).toBe(0)
})
})

describe('persistence', () => {
it('stores global reference to the applied interceptor', () => {
const interceptor = new Interceptor(symbol)
Expand Down
33 changes: 31 additions & 2 deletions src/Interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ export class Interceptor<Events extends InterceptorEventMap> {
runningInstance.emitter.removeListener(event, listener)
logger.info('removed proxied "%s" listener!', event)
})

return this
}

this.readyState = InterceptorReadyState.APPLIED
Expand Down Expand Up @@ -143,20 +145,47 @@ export class Interceptor<Events extends InterceptorEventMap> {
public on<EventName extends ExtractEventNames<Events>>(
eventName: EventName,
listener: Listener<Events[EventName]>
): void {
): this {
const logger = this.logger.extend('on')

if (
this.readyState === InterceptorReadyState.DISPOSING ||
this.readyState === InterceptorReadyState.DISPOSED
) {
logger.info('cannot listen to events, already disposed!')
return
return this
}

logger.info('adding "%s" event listener:', eventName, listener.name)

this.emitter.on(eventName, listener)
return this
}

public once<EventName extends ExtractEventNames<Events>>(
eventName: EventName,
listener: Listener<Events[EventName]>
): this {
const logger = this.logger.extend('once')
logger.info(
'adding a one-time "%s" event listener:',
eventName,
listener.name
)

this.emitter.once(eventName, listener)
return this
}

public off<EventName extends ExtractEventNames<Events>>(
eventName: EventName,
listener: Listener<Events[EventName]>
): this {
const logger = this.logger.extend('off')
logger.info('removing "%s" event listener:', eventName, listener.name)

this.emitter.off(eventName, listener)
return this
}

/**
Expand Down
29 changes: 26 additions & 3 deletions src/utils/AsyncEventEmitter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Logger } from '@open-draft/logger'
import { Emitter, EventMap, Listener } from 'strict-event-emitter'
import { nextTick } from './nextTick'
import { invariant } from 'outvariant'

export interface QueueItem<Args extends Array<unknown>> {
args: Args
Expand All @@ -17,13 +18,16 @@ export class AsyncEventEmitter<
> extends Emitter<Events> {
public readyState: AsyncEventEmitterReadyState

private logger: Logger
protected logger: Logger

protected wrappedListeners: Map<Function, Listener<any>>
protected queue: Map<keyof Events, Array<QueueItem<Events[any]>>>

constructor() {
super()

this.logger = new Logger('async-event-emitter')
this.wrappedListeners = new Map()
this.queue = new Map()

this.readyState = AsyncEventEmitterReadyState.ACTIVE
Expand All @@ -42,7 +46,7 @@ export class AsyncEventEmitter<
return this
}

return super.on(eventName, async (...args) => {
const wrappedListener: Listener<Events[EventName]> = async (...args) => {
// Event queue is always established when calling ".emit()".
const queue = this.openListenerQueue(eventName)

Expand All @@ -66,7 +70,13 @@ export class AsyncEventEmitter<
}
}),
})
})
}

// Associate the raw listener function with the wrapped listener
// to be able to remove this listener by the raw function reference.
this.wrappedListeners.set(listener, wrappedListener)

return super.on(eventName, wrappedListener)
}

public emit<EventName extends keyof Events>(
Expand Down Expand Up @@ -107,6 +117,19 @@ export class AsyncEventEmitter<
return super.emit(eventName, ...data)
}

public removeListener<EventName extends keyof Events>(
eventName: EventName,
listener: Listener<any>
): this {
const wrappedListener = this.wrappedListeners.get(listener)

if (!wrappedListener) {
return this
}

return super.removeListener(eventName, wrappedListener)
}

/**
* Returns a promise that resolves when all the listeners for the given event
* has been called. Awaits asynchronous listeners.
Expand Down

0 comments on commit a32813a

Please sign in to comment.