Node's EventEmitter is fire-and-forget: emit calls every listener synchronously and returns, ignoring anything a listener does asynchronously. That breaks down the moment listeners do real work — writing to a DB, calling an API, flushing a queue — and you need to know when they're all done, or run them one-at-a-time to avoid a stampede. An async event emitter makes emit return a promise that awaits its listeners, with a choice of serial or parallel dispatch.
Implement eventEmitterAsync() with on, once, off, emit, emitParallel, and listenerCount.
function eventEmitterAsync() {
return { on, once, off, emit, emitParallel, listenerCount };
}
const em = eventEmitterAsync();
em.on('save', async (row) => { await db.insert(row); });
em.on('save', async (row) => { await audit(row); });
await em.emit('save', row); // serial: insert finishes, THEN audit
await em.emitParallel('save', row); // both start at once, awaits both
const off = em.on('tick', fn);
em.once('ready', init); // auto-removed after the first emit
off(); // unsubscribe
emit awaits, serially — call listeners in registration order, awaiting each before the next; resolve to the array of their return values.emitParallel — start every listener at once and Promise.all them.emit rejects on the first throwing listener and skips the rest; emitParallel rejects if any listener rejects.once and unsubscribe — once fires a listener a single time (removed before it runs); on/once both return an unsubscribe. Snapshot the listener list in emit so on/off mid-dispatch don't disturb the round.Unlock the solution & editor
Runnable editor + tests
Solve in the browser with instant Jest feedback.
Detailed solutions
Walkthroughs, edge cases, and complexity notes.
Multi-framework variants
React, Vue, Vanilla, Angular — same question, different stacks.