What does it mean "event-driven" in JavaScript and Node.js?
Just starting out with JavaScript and "event-driven" is all over the place? Worry not and keep reading to learn more!
Event-driven and publish-subscribe
Event-driven architectures build on a common pattern in software development known as publish-subscribe or observer pattern.
In an event-driven architecture there are at least two actors: the subject and the observer.
The subject is like an FM radio, it broadcasts a message to any observer interested in listening what the subject has to say.
There could be just one or one hundred observers, it does not matter as long as the subject has some message to broadcast.
Keep in mind that event-driven, publish-subscribe, and observer pattern are not the same thing in practice, but ideally they use the same approach: an entity broadcasts a message while other entities listen for it.
The publish-subscribe pattern is old as me. It's been theorized around 1987, while the observer pattern appeared in the quintessential book "Design patterns" by the gang of four in 1994.
How does event-driven applies to JavaScript in the browser?
JavaScript runs in your browser thanks to an engine.
Most popular JavaScript engines are V8, used by Google Chrome and Node.js, SpiderMonkey for Firefox, and JavaScriptCore, used by Safari/WebKit.
JavaScript engines enhance the language by providing a rich environment, offering also an event-driven platform for JavaScript.
In practice, JavaScript in the browser can interact with HTML elements, which are event emitters, that is, subjects able to emit events.
Consider this trivial example, an HTML document with a button:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>What means "event-driven" in JavaScript?</title>
</head>
<body>
<div>
<button id="subscribe">SUBSCRIBE</button>
</div>
</body>
</html>
Without JavaScript the button is inanimate. Now, HTML buttons are elements of type HTMLButtonElement, and as with any HTML element they're connected to EventTarget
, the common ancestor of every HTML element.
Event targets in the browser are objects capable of emitting events: they're subjects in the observer pattern.
A bit confusing? Remember: a subject is the FM radio, so any HTML elements is like a broadcaster.
In a moment you'll see who the observers are.
Subject and observer in the browser
If HTML elements are subjects, who are the observers? Any JavaScript function registered as a listener can react to events in the browser.
Select an HTML element with JavaScript:
const btn = document.getElementById('subscribe');
and register a listener with addEventListener
:
const btn = document.getElementById('subscribe');
btn.addEventListener("click", function () {
console.log("Button clicked");
});
Here "click" is the event, button is the subject, or the emitter, and the function is a listener, or the observer.
To recap:
HTML elements are event emitters.
JavaScript functions registered as listeners are the observers.
All these components make an event-driven architecture. To try the code save this HTML file (or try it on Codepen), click the button and check out the browser's console:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>What means "event-driven" in JavaScript?</title>
</head>
<body>
<div>
<button id="subscribe">SUBSCRIBE</button>
</div>
</body>
<script>
const btn = document.getElementById('subscribe');
btn.addEventListener("click", function () {
console.log("Button clicked");
});
</script>
</html>
In the next section you'll see the same concepts, applied to Node.js.
How does event-driven applies to Node.js?
Node.js is an environment for running JavaScript outside of the browser (command line tools and server side) based on the V8 engine.
Much of what you do in Node.js is based on events. Most of the times you interact with an emitter object and some observers listening for messages.
In Node.js there isn't any HTML element, so, most events originate from processes, interactions with networks, files, and so on.
(Node.js will support EventTarget
in a future release).
Every event emitter in Node.js has a method named on
which takes at least two arguments:
- the name of the event to listen to
- a listener function
Let's make a practical example. Consider this simple Node.js server:
const net = require("net");
const server = net.createServer().listen(8081, "127.0.0.1");
server.on("listening", function () {
console.log("Server listening!");
});
server.on("connection", function (socket) {
console.log("Client connected!");
socket.end("Hello client!");
});
Here we create a server listening on port 8081, localhost. On the server object we call the on
method for registering two listeners function.
The listening event fires as soon as the server starts, while the connection event fires when a client connects to 127.0.0.1:8081 (give it a shot!).
In this example server
is the event emitter, that is, the subject. Listener functions on the other hand are observers.
Where those on
methods come from?
Getting to know the EventEmitter
Any event-driven module in Node.js extends a root class named EventEmitter
. In our previous example createServer
from the net module uses on
under the hood.
EventEmitter
in Node.js has two fundamental methods: on
and emit
.
If you want to draw a parallel with the browser you can think of EventEmitter
as of any HTML element able to emit events.
To listen for events in the browser you call addEventListener
on the subject object:
const btn = document.getElementById('subscribe');
btn.addEventListener("click", function () {
console.log("Button clicked");
});
In Node.js instead there is on
:
// omit
server.on("listening", () => {
console.log("Server listening!");
});
// omit
To be precise there is also an addListener
method on EventEmitter
. on
is an alias for it.
EventEmitter
has also an emit
method, useful when you want to broadcast a custom event (a message).
If you want to play with EventEmitter
import it from the "events" module and try to emit an event:
const EventEmitter = require("events");
const emitter = new EventEmitter();
emitter.on("customEvent", () => console.log("Got event!"));
emitter.emit("customEvent");
Run the code with Node.js, and you'll see "Got event" in the console.
Other examples of observer / publish-subscribe in JavaScript
JavaScript does not have native support for observables, but there's a proposal for adding them to the language.
RxJS is a library which brings the observer pattern to JavaScript.
Redux is an implementation of the publish-subscribe pattern in JavaScript. "It is a glorified" event emitter where state changes are dispatched to any observer who listens.
Modern browsers are shipping with the Intersection Observer API, another example of observer pattern in action.
Socket.IO is a library which makes heavy use of events.
Conclusion
I hope you learned something new from this post. You learned a lot of technical jargon, but in the end all boils down to a pattern invented roughly 30 years ago: publish-subscribe.
This pattern, also known as observer, is the basis for event-driven architectures we use today in JavaScript and Node.js.
Bears repeating that event-driven, publish-subscribe, and observer are not exactly the same thing: event-driven architectures build on publish-subscribe, and the observer pattern is more richer than DOM and Node.js events.
In the end they're all part of the same family.
Thanks for reading and stay tuned!