Skip to content

Instantly share code, notes, and snippets.

@forivall
Created August 7, 2025 03:46
Show Gist options
  • Save forivall/151da77abeeb5f8b8d1d00161eaa048a to your computer and use it in GitHub Desktop.
Save forivall/151da77abeeb5f8b8d1d00161eaa048a to your computer and use it in GitHub Desktop.
interface EventEmitterOn {
on(event: string, listener: (...args: any) => void): any;
}
type EventNames<T extends EventEmitterOn> = T extends {
on(event: infer N1, listener: any): any;
on(event: infer N2, listener: any): any;
on(event: infer N3, listener: any): any;
on(event: infer N4, listener: any): any;
on(event: infer N5, listener: any): any;
on(event: infer N6, listener: any): any;
on(event: infer N7, listener: any): any;
on(event: infer N8, listener: any): any;
on(event: infer N9, listener: any): any;
on(event: infer N0, listener: any): any;
}
?
| (string extends N1 ? never : N1)
| (string extends N2 ? never : N2)
| (string extends N3 ? never : N3)
| (string extends N4 ? never : N4)
| (string extends N5 ? never : N5)
| (string extends N6 ? never : N6)
| (string extends N7 ? never : N7)
| (string extends N8 ? never : N8)
| (string extends N9 ? never : N9)
| (string extends N0 ? never : N0)
: never;
type EventListener<
T extends EventEmitterOn,
N extends EventNames<T>,
> = T extends {
on(event: infer N1, listener: infer L1): any;
on(event: infer N2, listener: infer L2): any;
on(event: infer N3, listener: infer L3): any;
on(event: infer N4, listener: infer L4): any;
on(event: infer N5, listener: infer L5): any;
on(event: infer N6, listener: infer L6): any;
on(event: infer N7, listener: infer L7): any;
on(event: infer N8, listener: infer L8): any;
on(event: infer N9, listener: infer L9): any;
on(event: infer N0, listener: infer L0): any;
}
? N1 extends N
? L1
: N2 extends N
? L2
: N3 extends N
? L3
: N4 extends N
? L4
: N5 extends N
? L5
: N6 extends N
? L6
: N7 extends N
? L7
: N8 extends N
? L8
: N9 extends N
? L9
: N0 extends N
? L0
: never
: never;
type EventValue<
T extends { on(event: string, listener: (...args: any) => void): any },
N extends EventNames<T>,
> = N extends never
? never
: { event: N; args: Parameters<EventListener<T, N>> };
class EventsIterator<T extends EventEmitterOn, const N extends EventNames<T>>
implements AsyncIterableIterator<EventValue<T, N>, void, never>
{
private readonly queue: Array<EventValue<T, N>> = [];
private resolve!: () => void;
private promise: Promise<void> = new Promise((resolve) => {
this.resolve = resolve;
});
private finished: Promise<void>;
constructor(
private readonly emitter: T & Readable,
private readonly eventNames: N[],
) {
this.eventNames.forEach((event) => {
this.emitter.on(event, (...args: any) => {
this.queue.push({ event, args } as EventValue<T, N>);
this.emitter.pause();
this.resolve();
});
});
this.finished = finished(this.emitter);
}
[Symbol.asyncIterator]() {
return this;
}
async next() {
if (!this.queue.length) {
await Promise.race([this.promise, this.finished]);
this.promise = new Promise((resolve) => {
this.resolve = resolve;
});
}
const value = this.queue.shift();
if (!this.queue.length) {
this.emitter.resume();
}
return { value, done: !value } as IteratorResult<EventValue<T, N>, void>;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment