Readers like you help support MUO. When you make a purchase using links on our site, we may earn an affiliate commission. Read More.

Key Takeaways

  • Event-driven programming is a good choice for interactive apps, especially GUI apps, as it allows the code to respond to user actions in any order.
  • Event-driven programming is commonly used in web apps, where event listeners trigger when users interact with the DOM.
  • Implementing event-driven programming in Node.js is easy with the EventEmitter class, which allows you to create custom events and attach event listeners to handle them.

When building any software application, one crucial decision involves selecting the appropriate paradigm for your code.

Event-driven programming is a good choice for interactive apps that respond to user actions which may occur in any order. It’s a more popular paradigm with GUI apps than with command-line programs or embedded systems code.

What Are Events?

You can think of an event as an action or occurrence that your code can recognize and respond to. The system or a user can trigger an event and your code will usually register a function to handle it.

An example of a basic event is clicking a button to perform a particular action. The act of clicking the button fires an event, and the function that runs when the click occurs is called the event listener (or handler).

What Is Event-Driven Programming?

Event-driven programming is a programming paradigm in which an application’s execution flow depends on events that occur rather than being strictly sequential.

This paradigm is mostly used when building user interfaces and real-time applications, where an event such as a user’s action should trigger an action in the system.

The paradigm is very popular when building web apps, where event listeners trigger when users interact with the Document Object Model (DOM).

The following image visualizes how the flow works in event-driven programming. When an event occurs, the event channel receives it and passes it on to the appropriate listener to handle:

visualization of event-driven programming

Event-Driven Programming in Node.js

The JavaScript event loop is one of the fundamental concepts behind the asynchronous nature of the Node.js runtime. An event-driven architecture uses its built-in EventEmitter module to facilitate seamless execution flow.

With event-driven programming, Node.js lets you create server-side applications that can handle user interaction, I/O operations, and real-time data processing. This occurs in a non-blocking manner, resulting in enhanced performance and a smoother experience for the user.

Implementing event-driven programming in Node.js is easy when you understand the basics of defining, triggering, and handling events.

The EventEmitter Class

With the EventEmitter class in Node.js, you can create custom events and attach event listeners to handle them. To use the class in your code, import it from the events module like this:

 // CommonJS
const { EventEmitter } = require("events")

// ES6
import { EventEmitter } from "events"

The class and its member functions are then available for you to use in your application. To start emitting and handling events, initialize a new instance of the EventEmitter class.

For example:

 const FoodEvents = new EventEmitter()

This creates a new emitter object called FoodEvents that can emit events and register listeners. The EventEmmitter class provides three methods for listening to an event: on, addListener, and once.

The on method is the most basic function for adding event listeners, and addListener works in exactly the same way. They both accept the event name and a callback function as arguments. The callback is the actual handler function. You can use on and addListener interchangeably.

Here’s how you handle an event using the on method:

 FoodEvents.on("cookie_ready", (data) => {
    console.log("Cookie ready for packaging, data received: ", data);
})

Using addListener as a direct alternative for on:

 FoodEvents.addListener("cookie_ready", (data) => {
    console.log(
        "Cookie will now be packaged and sent out, data received: ",
        data
    );
})

Both these examples will add the callback to the array of event listeners for the cookie_ready event. If you use both, their callbacks will fire in order.

The once method registers a one-time event listener which runs the next time the event fires. After that, the system will remove it from the array of listeners.

Here’s how to use once to handle a one-time event:

 FoodEvents.once("cookie_sent", (data) => {
    console.log("Cookie is sent out, data received: ", data);
})

In this case, the emitter will only listen for the cookie_sent event once and remove the handler after it has run.

All three methods return the emitter, so you can chain calls to any one of them.

Don’t forget that, for a listener to handle an event, the application must emit that event at some point. Here’s some sample code to emit the cookie_ready event using the emit method:

 function bakeCookie() {
    console.log("Cookie is baking, almost ready...")

    setTimeout(() => {
        FoodEvents.emit("cookie_ready", { flavor: "vanilla cookie" })
    }, 3000)
}

bakeCookie()

When you run this code which prints a notice in the console that the cookie is baking, waits for 3 seconds, and emits the cookie_ready event, you will get an output like the image below:

console output image

This demonstrates how the event listeners run in the order you register them.

The EventEmitter class provides more methods, including:

  • removeListener: Removes an instance of a listener from the array of event listeners. The off method is also available for this purpose.
  • prependListener: This method also registers a listener but, instead of adding it to the end of the listeners array, it adds it to the beginning. It will then run before any other listeners you may have already registered.
  • prependOnceListener: This works just like prependListener, but the listener only runs once, as in the case of once.
  • removeAllListeners: This function removes all the registered listeners for a specific named event, or all listeners if you pass no argument to it.
  • listeners: Returns an array of listeners of the event name you pass to it as an argument.
  • eventNames: You can use this function to get all the event names for which you have already registered a listener.
  • setMaxListeners: Node.js throws a warning by default when you register more than 10 listeners for an event, to prevent memory leaks. You can adjust this default value using setMaxListeners. You can also check this value using getMaxListeners.

The events package provides comprehensive functionality for event-driven programming in Node.js.

What Are Some Event-Driven Programming Best Practices?

Every programming approach has its tradeoffs, and ignoring best practices can have adverse effects on your application. The following are some best practices to consider when building event-driven applications:

  • Use concise and descriptive names for events to enable a clean and maintainable codebase.
  • Adopt good error handling and logging practices, to allow for easy debugging of errors.
  • Avoid callback hell (nesting multiple callbacks) when writing event listeners. Use JavaScript promises instead.
  • Don’t create too many listeners for one event. Consider splitting the events and chaining them instead.

Build Applications With the Right Architecture

A general rule that applies to building software is to make appropriate architecture and design decisions. When you follow the wrong approach to building an application, you’ll face the consequences eventually.

Event-driven programming is a paradigm that can have a significant impact on an application’s architecture and performance. Whenever your application, or a part of it, depends on events to function, you should consider event-driven programming.