Event emitters and listeners in JavaScript

Michał Męciński
codeburst
Published in
6 min readJun 22, 2017

--

Many programming languages, including JavaScript, have the concept of events. They make it possible for multiple independent components to communicate with each other. What does it mean and why is that useful?

Introduction to events

Let’s say that we have an HTTP server. Whenever an HTTP request is received, we want the server to call a function defined in our code which handles the request and returns some response.

One way it could work is by passing the callback function directly to the server:

server.listen( function( request, response ) {
response.write( 'Hello' );
response.end();
} );

The function which is passed to the listen() method is called the “callback”. The server will call this function every time it receives an HTTP request. It doesn’t need to know anything about this function, other that it accepts two arguments.

Those of you who know Node.js will notice that this example is similar to the real http.Server. However, the code I’m showing here is only a hypothetical example for illustration purposes.

We may be interested not only in handling requests, but also in handling any potential errors. One way this could be done is by passing extra arguments to the callback:

server.listen( function( err, request, response ) {
if ( err ) {
// do something...
}
response.write( 'Hello' );
response.end();
} );

This pattern is often used in Node.js, but it’s not great. What if we have an existing code which expects that the callback is called with only two arguments? If we add error handling to the server, it will break that code. And what if we want to handle yet another condition, for example closing the server? Adding even more arguments to the callback is a bad idea.

Another solution is to use separate callbacks:

server.listen( function( request, response ) {
// handle the request...
}, function( err ) {
// handle the error...
} );

This will not break existing code and we can add more callbacks in the future, but this code will quickly become unreadable. It’s just impossible to remember the correct order of callbacks if there’s more than two of them.

Okay, so how about passing a handler object and explicitly naming each callback function?

server.listen( {
handleRequest: function( request, response ) {
// handle the request...
},
handleError: function( err ) {
// handle the error...
}
} );

That’s definitely better, but still not perfect. The problem is that now the server has to make a lot of assumptions about the object that is passed into it. For example, it needs to check whether it contains a particular function:

if ( handler.handleError )
handler.handleError( err );

Another problem is that sometimes we may need to use multiple handlers. For example, the request handler can be defined in one part of the application, and the error handler in another. We might even want to use multiple handlers for the same event — for example, one might send some response and another might write something to the log.

Luckily, Node.js contains the EventEmitter class which solves all these problems for us. If our hypothetical HTTP server used it, the example above could be written this way:

server.on( 'request', function( request, response ) {
// handle the request...
} );
server.on( 'error', function( err ) {
// handle the error...
} );
server.listen();

Now the the purpose of each callback function, or “listener”, is perfectly clear.

Also the server doesn’t have to know anything about the code that uses it. All it has to do to invoke, or “emit”, an event is this:

this.emit( 'request', request, response );

If there is no listener for the 'request' event, this code does nothing. If there are multiple listeners, they are all called, one after another, with the same arguments.

In fact, the standard http.Server class and many other classes in Node.js inherit EventEmitter.

Implementing an event emitter

If you implement your own class which is capable of emitting events, all you have to do is inherit the EventEmitter class. Then you can call the emit() method whenever you need, passing the name of the event and any number of arguments. For example:

const EventEmitter = require( 'events' );class MyClass extends EventEmitter {
doSomething() {
// do something...
if ( !err )
this.emit( 'success', result );
else
this.emit( 'error', err );
}
}

You can find the full API documentation of the EventEmitter class here.

The observer pattern

An interesting use of the event emitter is the so called “observer pattern”. Basically, there is a central event emitter object which has multiple attached “observer” objects. These observers are notified whenever the state of the central object is changed. This pattern can easily be implemented using events. But what can it be used for?

Imagine that you are writing a multiplayer game. Each player can make a move, and players should also know about other players’ moves. For example:

class Game extends EventEmitter {
move( player, direction ) {
this.position[ player.id ].x += direction.x;
this.position[ player.id ].y += direction.y;
this.emit( 'playerMoved', player,
this.position[ player.id ] );
}
}

In this case, each player which listens to the 'playerMoved' event will be notified about all moves, those performed by other players and by themselves:

game.on( 'playerMoved', ( player, newPosition ) => {
if ( player != this ) {
// do something...
}
} );
// ...game.move( this, { x: 1, y: 2} );

You can read more about implementing multiplayer games in Node.js here

Events in client-side code

Event emitters are useful not only when programming in Node.js, but also in client-side code which runs in the browser. In fact, if you use jQuery or Vue.js or any other client-side library, they all use the concept of events in one way or another. This includes both standard DOM events, such as mouse clicks, and custom events defined by your components.

If you use a tool such as Webpack or Browserify to bundle your client-side code, you can also use the EventEmitter class which works in the same way as the one provided by Node.js. This is an example of a very simple UI component which implements custom events:

const EventEmitter = require( 'events' );class MyComponent extends EventEmitter {
constructor() {
super();
$( '#red-button' ).click(
() => this.emit( 'button', 'red' );
);
$( '#blue-button' ).click(
() => this.emit( 'button, 'blue' );
);
)
)

Note that when we use an arrow function, this refers to the instance of the MyComponent class, not to the button element that was clicked. That’s a subtle but very important difference between arrow function and regular functions.

Removing event listeners

We already know that we can attach an event listener to an emitter using the on() method. There is also a removeListener() method which can remove an existing listener, so it will no longer be called when the event is emitted.

Removing existing event listeners is important when you have to control the life-cycle of objects in your application. Let’s return to the multiplayer game example. A player may disconnect from the game, and we want the player object to be removed from the game. If you don’t call removeListener() to disconnect all listeners associated with that player, they will still be called on each event, and the player object will never be removed from memory.

class Player {
constructor( game ) {
this.game = game;
this.listener = ( player, newPosition ) => {
if ( player != this ) {
// do something...
}
};
this.game.on( 'playerMoved', this.listener );
}
removePlayer() {
this.game.removeListener( 'playerMoved', this.listener );
}
}

Note that you have to store a reference to the the callback function to be able to remove it later, because it must be passed to removeListener().

This can become quite inconvenient when your class must listen to multiple events. Also it’s easy to forget to remove one of the events, which leaves a bunch of “zombie” players hanging around and leads to all sorts of hard to find bugs.

You can use a small but very handy tool called ultron. It automatically remembers all attached listeners and makes it possible to remove them all at once by simply calling remove(). So the above code could be rewritten as follows:

const Ultron = require( 'ultron' );class Player {
constructor( game ) {
this.ultron = new Ultron( this.game );
this.ultron.on( 'playerMoved', ( player, newPosition ) => {
if ( player != this ) {
// do something...
}
} );
}
removePlayer() {
this.ultron.remove();
}
}

That’s all for today, thank you for reading. Also, big thanks to Brandon Morelli for inviting me to the codeburst publication. It’s an honor to be among such great authors!

--

--