I have a generic helper function that allows me to await events on any EventEmitter.
import { type EventEmitter } from 'eventemitter3'
/** Returns a promise that resolves when the given event fires */
export const eventPromise = async (emitter: EventEmitter, event: string) =>
new Promise<any>(resolve => {
emitter.once(event, d => resolve(d))
})
So I can await the next foo event as follows:
await eventPromise(someEmitter, 'foo')
So far this works fine.
I'm using eventemitter3, which gives me strongly typed events:
type TestEvents = {
foo: (payload: { bar: string }) => void
}
class TestEmitter extends EventEmitter<TestEvents> {
test() {
this.emit('foo', { bar: 'pizza' })
}
}
So it seems like it should be possible for the return value of eventPromise to be strongly typed:
const testEmitter = new TestEmitter()
const { bar } = await eventPromise(testEmitter, 'foo')
But this gives me an error, Property 'bar' does not exist on type 'unknown'. (See this playground.)
I've gone around in circles quite a bit trying to get eventPromise to return a strongly typed value. This is the closest I've gotten:
export const eventPromise = async <
T extends EventMap,
K extends EventEmitter.EventNames<T> = EventEmitter.EventNames<T>,
>(
emitter: EventEmitter<T>,
event: K
) => {
return new Promise<Parameters<T[K]>[0]>(resolve => {
const listener = (payload: Parameters<T[K]>[0]) => resolve(payload)
emitter.once(event, listener as EventEmitter.EventListener<T, K>)
})
}
This compiles, but the return type is still unknown. (See this playground.)
I honestly feel like none of these gymnastics should even be necessary - shouldn't the original function have enough information for TypeScript to infer its return type?
What am I missing?
Clarification: This should work generically with any EventEmitter, not just one that handles TestEvents.

I was able to make it work with the code below.
I have extracted a definition of
TestMessagesmore for convenience, the relevant differences with the code you have shown are ineventPromise, namely:eventPromisecould be abstracted further in order to take emitters with arbitrary events in input, so it should become generic, but whether that is even needed already depends on specific requirements.Link to playground