Zagonine
Zagonine

Reputation: 2313

When should I use EventEmitter?

I read a lot of things about EventEmitter. But I do not know in which case I need to use it in my Node.js application.

Upvotes: 33

Views: 14885

Answers (3)

Yilmaz
Yilmaz

Reputation: 49749

  • The EventEmitter should be used when the same event can occur multiple times, or may not occur at all. A callback, in fact, is expected to be invoked exactly once, whether the operation is successful or not. Callback means call me when you are ready

  • An API that uses callbacks can notify only one particular callback while using an EventEmitter allows us to register multiple listeners for the same event.

  • Use event emitter if you need to notify the user of a state change.

  • For testing purpose, if you want to make sure a function is called inside a function, emit an event.

Upvotes: 6

OSGI Java
OSGI Java

Reputation: 645

Node.js Event Emitter is used when there is a desire to decompose a codebase into components or services invoked using a pub-sub like asynchronous pattern. However, typically when we talk about pub-sub pattern we refer to distributed decomposed systems. This is not the case here as all components exist in the same code repository and run in the same Node.js runtime.

Keep in mind that using Node.js Event Emitter does not make our code automatically non-blocking, asynchronous. A special care needs to be taken so that event listeners (subscribers) do not block each other, that is the event listeners should execute code asynchronously.

In addition, when using this pattern event emitters (publishers) do not care about the result of the actions taken by the event listeners. There is no callback or return value. If these actions are critical then failures need to be handled.

Code examples:

/**
 * When event listeners execute synchronous blocking code as seen in this example,
 * the next listener is not notified until the first listener completes execution
 * of the synchronous blocking code.
 *
 * Here is an output from running this code:
 *
 * 11:16:40 Listener 1 - processing event
 * 11:16:45 Listener 1 - processed: Test Event
 * 11:16:45 Listener 2 - processing event
 * 11:16:45 Listener 2 - processed: Test Event
 */

const { EventEmitter } = require('events');

const time = () => {
  const currentDate = new Date();
  return `${currentDate.getHours()}:${currentDate.getMinutes()}:${currentDate.getSeconds()}`;
};

const EventBus = new EventEmitter();

// Listener 1
EventBus.on('event', (message) => {
  console.log(`${time()} Listener 1 - processing event`);
  for (let i = 0; i < 6e9; i += 1) {
    // Intentionally empty
  }
  console.log(`${time()} Listener 1 - processed: ${message}`);
});

// Listener 2
EventBus.on('event', (message) => {
  console.log(`${time()} Listener 2 - processing event`);
  console.log(`${time()} Listener 2 - processed: ${message}`);
});

// Emitting event
EventBus.emit('event', 'Test Event');

/**
 *
 * To take full advantage of EventListener the listeners should execute
 * asynchronous non-blocking code. However, wrapping a synchronous code
 * into an async function is not enough. The 2nd listener is still
 * blocked and waiting for the async function to complete
 *
 * Here is an output from running this code:
 * 11:13:52 Listener 1 - processing event
 * 11:13:52 Listener 1 - about to await
 * 11:13:57 Listener 2 - processing event
 * 11:13:57 Listener 2 - processed: Test Event
 * 11:13:57 Listener 1 - await completed
 * 11:13:57 Listener 1 - processed: Test Event
 */

const { EventEmitter } = require('events');

const time = () => {
  const currentDate = new Date();
  return `${currentDate.getHours()}:${currentDate.getMinutes()}:${currentDate.getSeconds()}`;
};

const EventBus = new EventEmitter();

// Listener 1
EventBus.on('event', async (message) => {
  console.log(`${time()} Listener 1 - processing event`);

  async function extracted() {
    for (let i = 0; i < 6e9; i += 1) {
      // Intentionally empty
    }
  }

  console.log(`${time()} Listener 1 - about to await`);
  await extracted();
  console.log(`${time()} Listener 1 - await completed`);
  console.log(`${time()} Listener 1 - processed: ${message}`);
});

// Listener 2
EventBus.on('event', (message) => {
  console.log(`${time()} Listener 2 - processing event`);
  console.log(`${time()} Listener 2 - processed: ${message}`);
});

// Emitting event
EventBus.emit('event', 'Test Event');
/**
 *
 * To take full advantage of EventListener the listeners should execute
 * asynchronous non-blocking code. Here we are using setTimeout() in order
 * to execute code asynchronously.
 *
 * Here is an output from running this code:
 *
 * 11:45:54 Listener 1 - processing event
 * 11:45:54 Listener 1 - about to execute setTimeout
 * 11:45:54 Listener 1 - setTimeout completed
 * 11:45:54 Listener 1 - processed: Test Event
 * 11:45:54 Listener 2 - processing event
 * 11:45:54 Listener 2 - processed: Test Event
 * 11:45:59 Listener 1 - finished the long loop
 */

const { EventEmitter } = require('events');

const time = () => {
  const currentDate = new Date();
  return `${currentDate.getHours()}:${currentDate.getMinutes()}:${currentDate.getSeconds()}`;
};

const EventBus = new EventEmitter();

// Listener 1
EventBus.on('event', async (message) => {
  console.log(`${time()} Listener 1 - processing event`);

  function extracted() {
    for (let i = 0; i < 6e9; i += 1) {
      // Intentionally empty
    }
    console.log(`${time()} Listener 1 - finished the long loop`);
  }

  console.log(`${time()} Listener 1 - about to execute setTimeout`);
  setTimeout(extracted, 0);
  console.log(`${time()} Listener 1 - setTimeout completed`);
  console.log(`${time()} Listener 1 - processed: ${message}`);
});

// Listener 2
EventBus.on('event', (message) => {
  console.log(`${time()} Listener 2 - processing event`);
  console.log(`${time()} Listener 2 - processed: ${message}`);
});

// Emitting event
EventBus.emit('event', 'Test Event');
/**
 *
 * To take full advantage of EventListener the listeners should execute
 * asynchronous non-blocking code. Here we are using setImmediate() in order
 * to execute code asynchronously.
 *
 * Here is an output from running this code:
 *
 * 12:1:3 Listener 1 - processing event
 * 12:1:3 Listener 1 - about to execute setImmediate
 * 12:1:3 Listener 1 - setImmediate completed
 * 12:1:3 Listener 1 - processed: Test Event
 * 12:1:3 Listener 2 - processing event
 * 12:1:3 Listener 2 - processed: Test Event
 * 12:1:9 Listener 1 - finished the long loop
 */

const { EventEmitter } = require('events');

const time = () => {
  const currentDate = new Date();
  return `${currentDate.getHours()}:${currentDate.getMinutes()}:${currentDate.getSeconds()}`;
};

const EventBus = new EventEmitter();

// Listener 1
EventBus.on('event', async (message) => {
  console.log(`${time()} Listener 1 - processing event`);

  function extracted() {
    for (let i = 0; i < 6e9; i += 1) {
      // Intentionally empty
    }
    console.log(`${time()} Listener 1 - finished the long loop`);
  }

  console.log(`${time()} Listener 1 - about to execute setImmediate`);
  setImmediate(extracted);
  console.log(`${time()} Listener 1 - setImmediate completed`);
  console.log(`${time()} Listener 1 - processed: ${message}`);
});

// Listener 2
EventBus.on('event', (message) => {
  console.log(`${time()} Listener 2 - processing event`);
  console.log(`${time()} Listener 2 - processed: ${message}`);
});

// Emitting event
EventBus.emit('event', 'Test Event');

Upvotes: 5

Chazzu
Chazzu

Reputation: 980

Whenever it makes sense for code to SUBSCRIBE to something rather than get a callback from something. The typical use case would be that there's multiple blocks of code in your application that may need to do something when an event happens.

For example, let's say you are creating a ticketing system. The common way to handle things might be like this:

function addTicket(ticket, callback) {
    insertTicketIntoDatabase(ticket, function(err) {
        if (err)
            return handleError(err);

        callback();
    });
}

But now, someone has decided that when a ticket is inserted into the database, you should email the user to let them know. That's fine, you can add it to the callback:

function addTicket(ticket, callback) {
    insertTicketIntoDatabase(ticket, function(err) {
        if (err)
            return handleError(err);

        emailUser(ticket, callback);
    });
}

But now, someone wants to also notify another system that the ticket has been inserted. Over time, there could be any number of things that should happen when a ticket is inserted. So let's change it around a bit:

function addTicket(ticket, callback) {
    insertTicketIntoDatabase(ticket, function(err) {
        if (err)
            return handleError(err);

        TicketEvent.emit('inserted', ticket);
        callback();
    });
}

We no longer need to wait on all these functions to complete before we notify the user interface. And elsewhere in your code, you can add these functions easily:

TicketEvent.on('inserted', function(ticket) {
    emailUser(ticket);
});

TicketEvent.on('inserted', function(ticket) {
    notifySlack(ticket);
});

Upvotes: 80

Related Questions