DMarlon Studio

Lesson 19 · 19 of 20

Events with EventEmitter · JavaScript

Connect parts of a Node program with EventEmitter: emit, on, once, multiple listeners and the special error event.

JavaScript · Events with EventEmitter in DMarlon Studio: the example's code and its output in the terminal

EventEmitter, from Node's events module, implements the publish/subscribe pattern: an object announces that something happened and whoever is interested reacts, without either side knowing the other. Shop extends EventEmitter and, in buy(), emits order when a sale goes through or soldout when stock runs out.

`on(event, fn)` registers a listener. One event can have several: one logs the order and another alerts the warehouse when left reaches zero. The arguments passed to `emit` (an object here; the item and the customer for soldout) reach every listener unchanged.

Listeners run synchronously and in the order they were registered, inside the emit call itself. `once` registers a listener that only runs the first time and then removes itself, which is why first order of the day! appears once and listenerCount("order") is 2 at the end.

The `error` event is special: if it is emitted and nobody is listening, Node throws the error, and if that is not caught, the program exits. So it is good practice to always register an error listener. This pattern is everywhere in Node (servers, streams, processes) and in Electron, one of DMarlon Studio's targets, where the window and the main process talk through events.

Code · main.js

const EventEmitter = require("events");// A class that extends EventEmitter can announce what happens inside it.class Shop extends EventEmitter {  constructor(stock) {    super();    this.stock = stock;  }  buy(customer, item) {    if (!this.stock[item]) return this.emit("soldout", item, customer);    this.stock[item] -= 1;    this.emit("order", { customer, item, left: this.stock[item] });  }}const shop = new Shop({ mug: 2 });shop.on("order", (o) => console.log(`Order: ${o.customer} bought a ${o.item}`));shop.on("order", (o) => o.left === 0 && console.log(`  warehouse: restock ${o.item}`));shop.once("order", () => console.log("  first order of the day!"));shop.on("soldout", (item, who) => console.log(`Sorry ${who}, the ${item} is sold out`));shop.buy("Ana", "mug");shop.buy("Luis", "mug");shop.buy("Marta", "mug");console.log("Listeners for order:", shop.listenerCount("order"));// "error" is special: emitting it with no listener throws.try {  shop.emit("error", new Error("payment gateway down"));} catch (err) {  console.log("Nobody listened:", err.message);}shop.on("error", (err) => console.log("Logged:", err.message));shop.emit("error", new Error("payment gateway down"));

Output · node main.js

Order: Ana bought a mug
  first order of the day!
Order: Luis bought a mug
  warehouse: restock mug
Sorry Marta, the mug is sold out
Listeners for order: 2
Nobody listened: payment gateway down
Logged: payment gateway down

Key points

  • emit announces an event with its data; on listens for it.
  • Listeners run synchronously, in registration order.
  • once listens only the first time.
  • An error event with no listener throws an exception.

Exercise

Add a lowstock event that fires when fewer than 2 units of an item are left, and use off with a named function to remove the warehouse listener after the first restock.