NodeJS Event Emitter
Tags: node, javascript, typescript
Published: 2022-07-08 07:56:00

Node JS, a JavaScript runtime that is built on top of the V8 JavaScript engine, the same one that powers Google's Chromium browsers. NodeJS has a built in event Emitter which we can inherit in our classes to build an event driven architecture. The API for interacting with the Event Emitter is nothing short of straight forward, we can see the benefit of this style by writing code that will only trigger when a certain event happens, just like the sprinklers turn on when the fire alarm goes off. We are going to explore  how to leverage the built in Event Emitter in Node js to write event driven systems, let's dive in.

## What is Event Driven Architecture
Event driven architecture is a programming paragdim where the control flow of a program is determined by a sequence of events. There is an event listener that sits and waits for events to occur which it will listen to, we can bind code to the listener thus whenever the event listener subscribes to an event, if a callback function is passed to the listener, the callback code will be called. 

## Under the hood 
In NodeJS there is an event loop which is the name for the data structure that is the Event listener, this allows NodeJs to handle multiple concurrent request while running on a single thread, allowing synchronous code to behave in an asynchronous manner.

Much of the NodeJS core API is built around an idiomatic asynchronous event-driven architecture, note that NodeJS also allows us to spin up multiple child process and worker threads, we are not going to dive into much detail about asynchronous programming in JavaScript but let's get a high level overview of how the event listener works.

All synchronous code gets executed in the call stack, you know in LIFO (Last-in-first-out) manner, event driven code do not follow this manner, the function attached to the event listener is added to the Event's list of internal listeners, when the event is fired, the function attached to the listener is pushed on to the call stack. When the EventEmitter object emits an event, all of the functions attached to that specific event are called synchronously. Any values returned by the called listeners are ignored and discarded. functions in an event's listeners are called in a LIFO (last-in-first-out) manner.

## Using the event emitter
To use the event emitter in our code we have to import it from the event emitter module that comes baked in with NodeJS. Our classes can then extend from the event emitter.

```javascript
import EventEmitter from 'node:events';

Class Alarm extends EventEmitter {};
```
We now have a starting point, let us discuss some implications of the above code snippet. Our alarm is now a sub class of the EventEmitter and has inherited some interesting methods. 

We can now add events to the Alarm, while registering a callback code that will be fired when the event is triggered. We can add events to any Alarm by calling the `addEventListener` directly on any instance of the Alarm.  This function accepts two arguments, a `string` or `property-key` which is the name of the event, by which we can identify it and also emit it. 

In this process we are registering the  callback function along with it's event to the list of internal listeners for that alarm. To fire the call back function we call the `emit` function passing the name of the event we want to trigger, this will call all the functions registered to that event synchronously. 

```javascript
let FireAlarm = new Alarm();

FireAlarm.addEventListener('smoke', () => {
  console.log('sprinkling water');
})

function triggerAlarm(smoke) {
  if (smoke) {
    FireAlarm.emit('smoke');
  }
}

triggerAlarm(true)

// sprinkling water
```
We can also accepts parameter when we define the callback function, when we emit the event we pass the arguments right after the event name we are emitting, they will be passed to the event listener function.

```javascript
let FireAlarm = new Alarm();

FireAlarm.addEventListener('smoke', (threat, Level) => {
  if (threatLevel <= 1) {
    console.log('sprinkling water');
  } else {
   console.log('flooding the building');
  }
})

function triggerAlarm(smoke, threatLevel) {
  if (smoke) {
    FireAlarm.emit('smoke', threatLevel);
  }
}

triggerAlarm(true, 1);
triggerAlarm(true, 2);

// sprinkling water
// flooding the building
```

There exists an alias for `addEventListener`, we can use `on` to register an event. We can also register an event that can only be triggered once using the `once` Method on the `EventEmitter`. The emitter will only fire the callback function for this listener at most only once, it is then unregistered from the emitter's internal list of listeners. 

```javascript
let FireAlarm = new Alarm();

FireAlarm.once('fire', () => {
  console.log('on fire');
})

FireAlarm.emit('fire');
// on fire
FireAlarm.emit('fire')
// nothing happens
```
It is possible to get the events registered on emitter by calling the `eventNames` Method on the emitter, this will return an array that contains the events that are registered on the emitter. 

```javascript
Let events = FireAlarm.eventNames();

console.log(events);

// ['smoke', 'fire']
```

We can also set a limit to the amount of listeners we can register to an emitter, if there is a need for this we can call `setMaxListeners(n)` to cap the number of listeners that can be attached to an instance, to get the maximum number of listeners on an emitter we can call the `getMaxListeners` . If we don't set the max listener by calling `setMaxListener()` then `getMaxListeners()` will return undefined. 

We can also get the sum of callback function that are registered to a particular event by calling `listernerCount(eventName)` which will return an integer.

```javascript
FireAlarm.setMaxListeners(4);

let smokeListener = FireAlarm.listenerCount('smoke');

console.log(smokeListener);
// 1
console.log(FireAlarm.getMaxListeners())
// 4
```
If we are more interested in getting the functions that are tied to an event the emitter provides the `listerners(eventName)` method, when this method is called it returns to us and array of the functions that are registered to that event. 

This is quite useful especially when we want to access a particular listener on an emitter, say remove that particular listener. We need the name of the event and the listener that we want to remove, we can then pass them as arguments to `emitter.off(eventName, listener)` this unsubscribes a listener from an event, it is an alias for `removeEventListener()`
Which also does the same thing.

```javascript
// assuming there is no way get the function
// inside here

const fireAlarmListeners = FireAlarm.listeners('smoke')

console.log(fireAlarmListeners);
[ Function ]

FireAlarm.of('smoke', fireAlarmListeners[0])

```

There are more methods on the event emitter than we can cover here but you can look at the [official Nodejs documentation](https://nodejs.org/api/events.html) for more information, hope you found this interesting and useful, drop a thing or two you know about Events in Node JS that I left out of this article. Definitely add your thoughts and experience with the event listener.