Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/additional-details.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
---
Expand Down
37 changes: 36 additions & 1 deletion src/main.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,4 +205,39 @@ test('broadcast channel can be set after emitter is created', async () => {

expect(handlerA).toHaveBeenCalledOnce()
expect(handlerB).toHaveBeenCalledOnce()
})
})

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')
})
40 changes: 40 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ export type GlobalEvent<T extends Events> = {
}
}[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
}
Expand Down Expand Up @@ -109,6 +119,35 @@ export function createEmitter<T extends Events>(options?: EmitterOptions) {
on(event, callback)
}

function next(options?: NextOptions): Promise<GlobalEvent<T>>
function next<E extends Event>(event: E, options?: NextOptions): Promise<EventPayload<E>>
function next<E extends Event>(eventOrOptions?: E | NextOptions, options?: NextOptions): Promise<GlobalEvent<T> |EventPayload<E>> {
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<T>): void
function off<E extends Event>(event: E): void
function off<E extends Event>(event: E, handler: Handler<EventPayload<E>>): void
Expand Down Expand Up @@ -159,6 +198,7 @@ export function createEmitter<T extends Events>(options?: EmitterOptions) {
on,
off,
once,
next,
emit,
clear,
setOptions,
Expand Down