Event emitters
EventTarget with CustomEvent
Baseline widely available
An application event bus is usually a small class with on, off and emit. EventTarget is that class, already in the platform: make a bare new EventTarget(), or extend it, then dispatch a CustomEvent and listen with addEventListener. Passing an AbortSignal to addEventListener takes a whole group of listeners off in one abort() call, which is the part hand-rolled emitters usually leave to the caller.
When this applies
Passing messages between parts of an app through a small event bus.
The native approach
const bus = new EventTarget();
const controller = new AbortController();
bus.addEventListener(
"cart:add",
(event) => render(event.detail.sku),
{ signal: controller.signal },
);
bus.dispatchEvent(new CustomEvent("cart:add", { detail: { sku: "A1" } }));
// Removes every listener registered with this signal.
controller.abort();When the dependency is still right
An answer that always says "the platform covers it" is worse than no answer. These are the cases where this one does not hold.
- Your emit calls pass several arguments. A CustomEvent carries one detail property, so each emit has to become an object and each handler reads event.detail.
- You use wildcard listeners or read the registered listener list, as mitt's all map allows. EventTarget exposes neither.
- You need the bus in Node below 15.0.0, where EventTarget is not a global. Node's own EventEmitter covers that without a dependency.
- You depend on the library's dispatch ordering or on re-entrant emits behaving a particular way, which DOM event dispatch does not promise to match.
- Your support target reaches below Safari 14, where new EventTarget() cannot be constructed, or below Safari 15, where addEventListener ignores the signal option and listeners never come off.
- The package is also pulled in transitively by something else, so dropping your direct dependency does not remove the bytes from the bundle.