diff --git a/docs/additional-details.md b/docs/additional-details.md index e1c310a..e5655bc 100644 --- a/docs/additional-details.md +++ b/docs/additional-details.md @@ -54,6 +54,26 @@ Listen for a single event and then automatically remove the handler emitter.once(...) ``` +## Waiting for an event + +Wait for an event to be emitted and get the payload using the `next` method. + +```ts +const payload = await emitter.next() // Wait for any event +const payload = await emitter.next('hello') // Wait for the hello event +``` + +You can also pass an options object to the `next` method to set a timeout. + +```ts +const payload = await emitter.next({ timeout: 1000 }) // Wait for any event with a timeout of 1 second +const payload = await emitter.next('hello', { timeout: 1000 }) // Wait for the hello event with a timeout of 1 second +``` + +:::info +If the event is not emitted before the timeout, the `next` method will reject with an `EmitterTimeoutError`. +::: + ## Removing listeners ### Return Value diff --git a/docs/index.md b/docs/index.md index 4d7bdae..c3bd1bd 100644 --- a/docs/index.md +++ b/docs/index.md @@ -19,7 +19,7 @@ features: - title: Type safety details: Type safe events and payload - title: Useful API - details: Includes on, off, once, emit, and clear + details: Includes on, off, once, next, emit, and clear - title: Support Global Handlers details: Setup handlers that run on all events --- diff --git a/src/main.spec.ts b/src/main.spec.ts index 6df7bef..c1fc054 100644 --- a/src/main.spec.ts +++ b/src/main.spec.ts @@ -205,4 +205,39 @@ test('broadcast channel can be set after emitter is created', async () => { expect(handlerA).toHaveBeenCalledOnce() expect(handlerB).toHaveBeenCalledOnce() -}) \ No newline at end of file +}) + +test('next without event returns the global event payload', async () => { + const emitter = createEmitter<{ hello: string }>() + + const event = emitter.next() + + emitter.emit('hello', 'world') + + await expect(event).resolves.toEqual({ + kind: 'hello', + payload: 'world', + }) +}) + +test('next with event returns the event payload', async () => { + const emitter = createEmitter<{ hello: string }>() + + const event = emitter.next('hello') + + emitter.emit('hello', 'world') + + await expect(event).resolves.toEqual('world') +}) + +test('next with timeout rejects if the event is not emitted', async () => { + vi.useFakeTimers() + const emitter = createEmitter() + + await expect(() => { + const payload = emitter.next({ timeout: 100 }) + vi.advanceTimersByTime(100) + + return payload + }).rejects.toThrowError('Timeout waiting for global event after 100ms') +}) diff --git a/src/main.ts b/src/main.ts index 0d5277c..08fa655 100644 --- a/src/main.ts +++ b/src/main.ts @@ -15,6 +15,16 @@ export type GlobalEvent = { } }[keyof T] +export type NextOptions = { + timeout?: number +} + +export class EmitterTimeoutError extends Error { + constructor(event: string, timeout: number) { + super(`Timeout waiting for ${event} event after ${timeout}ms`) + } +} + type EmitterState = { channel?: BroadcastChannel | null } @@ -109,6 +119,35 @@ export function createEmitter(options?: EmitterOptions) { on(event, callback) } + function next(options?: NextOptions): Promise> + function next(event: E, options?: NextOptions): Promise> + function next(eventOrOptions?: E | NextOptions, options?: NextOptions): Promise |EventPayload> { + const event = typeof eventOrOptions === 'string' ? eventOrOptions : undefined + const { timeout } = typeof eventOrOptions === 'object' ? eventOrOptions : options ?? {} + + if(event) { + return new Promise((resolve, reject) => { + once(event, resolve) + + if(timeout) { + setTimeout(() => { + reject(new EmitterTimeoutError(event, timeout)) + }, timeout) + } + }) + } + + return new Promise((resolve, reject) => { + once(resolve) + + if(timeout) { + setTimeout(() => { + reject(new EmitterTimeoutError('global', timeout)) + }, timeout) + } + }) + } + function off(globalEventHandler: GlobalEventHandler): void function off(event: E): void function off(event: E, handler: Handler>): void @@ -159,6 +198,7 @@ export function createEmitter(options?: EmitterOptions) { on, off, once, + next, emit, clear, setOptions,