Understanding 'events' Library in Nodejs with the help of Custom Polyfill

Understanding 'events' Library in Nodejs with the help of Custom Polyfill

What is EventEmitter in Node.js?

The EventEmitter is a class that facilitates communication/interaction between objects in Node.js. The EventEmitter class can be used to create and handle custom events.

EventEmitter is at the core of Node asynchronous event-driven architecture. Many of Node's built-in modules inherit from EventEmitter including prominent frameworks like Express.js. An emitter object has two main features:

  • Emitting name events.

  • Registering and unregistering listener functions.

const events = require('events');
const eventEmitter = new events.EventEmitter();

function listener(code, msg) {
   console.log(`status ${code} and ${msg}`);
}

eventEmitter.on('status', listener); // Register listener
eventEmitter.emit('status', 200, 'ok');

// Output
status 200 and ok

How does the EventEmitter works in Node.js?

  • Event Emitter emits the data in an event called as message

  • A Listener is registered on the event message

  • when the message event emits some data, the listener will get the data

    Building Blocks:

    • .emit() - this method in event emitter is to emit an event in the module

    • .on() - this method is to listen to data on a registered event in node.js

    • .once() - it listens to data on a registered event only once.

    • .addListener() - it checks if the listener is registered for an event.

    • .removeListener() - it removes the listener for an event.

Custom Polyfil for eventEmitter class

Now let's write our custom eventEmitter

class Event{
    constructor(){
      this.events={}
    }

  /*
    - initialize empty list for a new event
    - push callback to the event list
  */
  on(eventName, callBack) {
    if (!this.events[eventName]) {

      this.events[eventName] = [];  
    }
    this.events[eventName].push(callBack); 
  }

  /*
   Start calling the callback functions that exists for a given
   eventName
   Note: callbacks are called in the sequence in which they're added       
  */
  emit(eventName, ...args) {
    if (this.events[eventName]) {
      this.events[eventName].forEach((callBack) => callBack(...args));
    }
  }

}

const eventEmitter = new Event();
eventEmitter.on("firstListener", () => console.log("first listener executed"));

eventEmitter.emit("firstListener")

//output 
// first listener executed

Now let's add a few more additional methods for the Event class like removeListener,once and listenerCount.

 removeListener(eventName, callBack) {
    if (this.events[eventName]) {
      this.events[eventName] = this.events[eventName].filter(
        (c) => c !== callBack
      );
    }
  }


/*
Make a callBackOnce function that calls it once and immediately         removes it from the array.
Pass this function to .on() method
*/

  once(eventName, callBack) {
    const callBackOnce = (...args) => {
      callBack(...args); 
      this.removeListener(eventName, callBackOnce);
    };
    this.on(eventName, callBackOnce);
  }

/* Returns a number of listeners on an event */
  listenerCount(eventName) {
    return this.events[eventName] ? this.events[eventName].length : 0;
  }

So the complete code looks like

class Event{
    constructor(){
      this.events={}
    }

  /*
    - initialize empty list for a new event
    - push callback to the event list
  */
  on(eventName, callBack) {
    if (!this.events[eventName]) {

      this.events[eventName] = [];  
    }
    this.events[eventName].push(callBack); 
  }

  /*
   Start calling the callback functions that exists for a given
   eventName
   Note: callbacks are called in the sequence in which they're added       
  */
  emit(eventName, ...args) {
    if (this.events[eventName]) {
      this.events[eventName].forEach((callBack) => callBack(...args));
    }
  }


  removeListener(eventName, callBack) {
    if (this.events[eventName]) {
      this.events[eventName] = this.events[eventName].filter(
        (c) => c !== callBack
      );
    }
  }


/*
Make a callBackOnce function that calls it once and immediately         removes it from the array.
Pass this function to .on() method
*/

  once(eventName, callBack) {
    const callBackOnce = (...args) => {
      callBack(...args); 
      this.removeListener(eventName, callBackOnce);
    };
    this.on(eventName, callBackOnce);
  }

/* Returns a number of listeners on an event */
  listenerCount(eventName) {
    return this.events[eventName] ? this.events[eventName].length : 0;
  }

}

const eventEmitter = new Event();
eventEmitter.on("firstListener", () => console.log("first listener executed"));

eventEmitter.emit("firstListener")
//output 
// first listener executed

eventEmitter.once('once',()=>console.log('i am called only once')
eventEmitter.emit('once') 
eventEmitter.emit('once') 

//output 
// i am called only once
// no output

Conclusion

In conclusion, Event Emitter is a powerful and versatile tool in Node.js that allows developers to create complex and dynamic applications. It enables the creation of custom events that can trigger specific functions or actions, allowing for greater flexibility and control over the application behaviour.

Event Emitter also enables the communication between different parts of an application, making it possible for components to react to changes in other parts of the application. This allows for greater modularity and scalability, as applications can be easily expanded or modified without the need for extensive code changes.