Events Module
Intro
This is the eighth in a series of tutorials on Node.js.We created a project folder called nodejs-tutorial to hold example code from each tutorial.
The code for the whole series is available on GitHub at github.com/LearnByCheating/nodejs-tutorial
This tutorial covers the Events module.
Create a new directory in the nodejs-tutorial project named 8-events.
Then open your Terminal application and cd into the new folder.
Read it, watch it, do it, review it:
- There are both a written and accompanying video version of this tutorial. There are timestamps under the headings that align with the video version of the topic.
- Read it: For each topic heading, first read the topic.
- Watch it: Then watch the associated section of the video.
- Do it: Then follow the instructions to replicate the steps on your computer.
- The Node.js CheatSheet has a major category that aligns with this tutorial.
- Review it: When you are done with the whole tutorial, open the CheatSheet and review the Events category from this tutorial. Make sure you understand everything, and can refer back to the CheatSheet when you are working on your own projects in the future.
The Event Loop
[0:00 Video timestamp]JavaScript is event driven.
It registers listeners to listen for events.
When they occur a callback function is executed.
Examples of events in the web browser environment include a page being loaded, a button being clicked, a form being submitted, etc.
In the Node environment, events are handled with the Events module.
The Events Module
Node's Events module drives its asynchronous event-driven architecture.It consists of the EventEmitter class with methods for emitting and listening for events.
The Events module is used in:
- Node core modules such as fs, HTTP, and Process.stdin/stdout.
- It's also used in third party npm packages that involve Input/Output tasks such as querying a database.
- AND it can be used in your own modules if you have I/O tasks.
Some specific examples of how Events are used in Node core modules include:
- Timers: Waits for the timer to expire, then executes a callback function.
- Process.stdin/stdout: Listens for the data event when a user enters a message in the command line, then executes a callback function.
- File System: When reading a file or directory, waits for the data to be received, then executes a callback function.
- Stream module: Listens for the data event when a chuck of data is accumulated, then executes a callback function.
- Http module: Listens for a request event when an HTTP request is received by the server, then executes a callback function.
Create an event
[0:37 Video timestamp]As a JavaScript developer, you use events all the time. But in most cases you won't actually need to create your own events.
In case you do, or just to understand how it is done we'll create some basic events.
Create a file called basicEvent.js and populate it with the below:
nodejs-tutorial/8-events/basicEvent.js
const EventEmitter = require('events'); // Create an instance of the EventEmitter class. const emitter = new EventEmitter(); // Listen for the event, then execute a callback function. emitter.on('logIt', () => { console.log('LogIt event occurred'); }); // Emit the logIt event emitter.emit('logIt');
There are three steps to create a custom event.
1. Create an instance of the EventEmitter class:
The first step is to import the EventEmitter class from the events module and create an instance of that class.The format is:
const EventEmitter = require('events');
const emitter = new EventEmitter();
In our basicEvent module, we created an EventEmitter object and assigned it to a variable named emitter, but it can be any name.
2. Register one or more Listeners on an event:
The second step is to register a listener for the custom event.The format is:
emitter.on(eventName, function([args]) { /* handle event */ }
Chain the on method to the emitter object.
The on method takes two arguments.
- The first argument is the event name that it is listening for.
- The second argument is the callback function that gets executed when the event is emitted.
In our basicEvent module, our callback function just logs a message to the console.
3. Emit an event
The third step is to emit the event with the emit method.The format is:
emitter.emit(eventName[, ...args])
Chain the emit() method to the emitter instance.
The argument is the event name. The event name can be any name you want.
In our basicEvent module, we emit an event named logIt.
Execute the file:
Let's execute the file in the terminal:node basicEvent
It emits the event and executes the listener function, logging a message to the console.
Event example with arguments
[1:41 Video timestamp]Create another file called eventWithArgs.js and populate it with the below:
nodejs-tutorial/8-events/eventWithArgs.js
const EventEmitter = require('events'); // Create an instance of the EventEmitter class. const emitter = new EventEmitter(); // Listen for the event, then execute a callback function. emitter.on('logIt', (msg) => { console.log(msg); }); // Emit event, passing in a message as the second argument. emitter.emit('logIt', 'Log message 1'); emitter.emit('logIt', 'Log message 2');
We use the same three steps. The only difference is this time when we emit the logIt event
we pass an argument to the listener callback function.
You can pass as many arguments as you need, then set them as parameters in the callback function.
In this case it is the msg parameter.
Execute the file:
Execute the file in the terminal:node eventWithArgs
It should log the two messages.
Inherit EventEmitter
[2:10 through 3:50 Video timestamps]Let's create a third custom event.
This time, instead of using the EventEmitter class directly, we'll create a class that inherits from it.
This is what the core Node modules that use EventEmitter do behind the scenes.
We've seen it used by the fs File System module, the HTTP module, and the Process module's stdin/stdout methods.
Create another file called inheritEventEmitter.js and populate it with the below:
nodejs-tutorial/8-events/inheritEventEmitter.js
// 1) Create instance of a class that inherits from EventEmitter. const EventEmitter = require('events'); class Chat extends EventEmitter { constructor(topic) { super(); this.topic = topic; this.status = 'inactive'; } } const session = new Chat('Event Loop'); // 2a) Register listener for first occurrence of the begin event. session.once('begin', () => { session.status = 'active'; console.log(`Chat Session topic: ${session.topic}`); }); // 2b) Register listener for the send event. session.on('send', (participant, msg) => { if (session.status === 'inactive') { session.emit('error', new Error('Chat is not active')); } else { console.log(`${participant}: ${msg}`); } }); // 2c) Register listener for first occurrence of the end event. session.once('end', () => { session.status = 'inactive'; console.log(`Chat Session has ended.`); }); // 2d) Register listener for the error event. session.on('error', (err) => { console.error(err.message); }); // 3) Emit events session.emit('begin'); session.emit('send', 'Sheena', 'Can you explain the Event Loop?'); session.emit('send', 'Joey', 'Yes'); session.emit('send', 'Sheena', 'Will you?'); session.emit('send', 'Joey', 'No'); session.emit('end'); session.emit('send', 'Sheena', 'I have another question.');
In this file we are simulating a Chat application.
1. Create an instance of the EventEmitter class:
- At the top we import the EventEmitter class as usual.
- Then we create a Chat class that inherits from EventEmitter by using the extends keyword.
- In the constructor function when we inherit from a class we have to include the super() method before setting our own properties.
- Our Chat class has two properties, the topic and the status.
- We set the status value initially to "inactive".
- Then we instantiate a chat object.
- We are assigning it to a variable named session,
- And passing in the topic property value: "Event loop".
2. Register one or more Listeners on an event:
- The second step is to register listener functions. This time we have four events: begin, send, end, and error.
- Once method: We haven't used the once method yet. The once method is like the on method but it executes the callback only the first time the event is called.
- Begin event: We use the once method with the "begin" event.
In the callback we change the session.status property to "active",
Then log a message.
- Send event: The on method listens for the send event. The callback has parameters for the participant name and message.
- You can emit events from within an event listener function.
- In the send event callback/listener function, we have an if statement to check if the status is inactive.
- If so it emits an error event.
- Then the second argument creates a new error object with an error message that stays "Chat is not active".
- If the status is not inactive (i.e, if it's active),
then we log the two arguments passed in with the send event:
the participant's name and the chat message.
- Error event: The listener for the error event logs the error message to the console.
- End event: We also register a listener for the end event using the once method so it only executes one time.
- The callback function sets the session status to inactive and logs a message.
3. Emit an event
- The third event step is at the bottom where we emit all of our events.
- We'll first emit a begin event to make the session active.
- Then several send events which log the chat messages.
- The send event passes two arguments. The participant name and the message.
- The end event will set the session status to "inactive".
- Then we emit another send event which will trigger an error event since the chat session status is now inactive.
Execute the file:
Execute the file in the terminal:node inheritEventEmitter
And it logs the chat session.
Including logging the error message when Sheena sends a chat after the chat session ended.