Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nodejs: How to handle event listening between objects?

I currently have the problem that I have an object which should listen on another object.

The question is: how should I handle the subscription? Currently, I only know of two possible ways:

With registrator:

var Registrator = function() {
    this.listener = new Listener();
};

Registrator.prototype.register = function(subsriber) {
    var self = this;
    subscriber.on('event', function(info) {
        self.listener.doSomething(info);
    });

};

In constructor:

var Subscriber = function() {
    var listener = require('./instanced-listener');
    this.on('event', function(info) {
        listener.doSomething(info);
    });

};

As you can see, both methods are not that neat. Is there another pattern which I could use for this problem?

PS:

If the example looks too abstract, here is the source:

https://github.com/bodokaiser/documents/blob/master/lib/repository.js

Check the constructor to see my unhappiness :)

like image 537
bodokaiser Avatar asked Dec 24 '12 11:12

bodokaiser


People also ask

What happens if an error event is emitted through an EventEmitter and nothing listens to it?

Any listeners for the error event should have a callback with one argument to capture the Error object and gracefully handle it. If an EventEmitter emits an error event, but there are no listeners subscribed for error events, the Node. js program would throw the Error that was emitted.

Why is that using async function with event handler is problematic?

Using async functions with event handlers is problematic, because it can lead to an unhandled rejection in case of a thrown exception: const ee = new EventEmitter(); ee. on('something', async (value) => { throw new Error('kaboom'); });

How do Node.js events work?

Since Node. js applications are event-driven, the events are triggered on its associated event handler. An event handler is nothing but a callback function called when an event is triggered. The main loop listens to the event triggers and then calls the corresponding event handler.


2 Answers

It really depends on how the objects will broadcast and listen and how coupled or decoupled you require them to be. Generally speaking, the best use of this type of event architecture is where an object can indiscriminately announce events to other objects and those objects can choose to listen (register) or stop listening (unregister) at their own discretion.

1. The listener is dependent on the broadcaster

It might make sense in some scenarios to make the listener a dependent of the broadcaster, in which case it will be strongly coupled to the event name and the arguments of the event being announced by that broadcaster.

var Listener = function(broadcaster) {
  var self = this;
  broadcaster.on("event", function(info) {
    self.doSomething(info);  
  });
};

Other listener classes can also be created and register themselves with the same broadcaster. The broadcaster has no knowledge of the listeners only the listener is aware of the relationship.

2. The broadcaster is dependent on the listener

The broadcaster announces events only for the listener. In this scenario the event architecture may be overkill, you could replace the events with direct calls. Your second example hints at this because of the one to one relationship with the listener in the broadcaster's constructor.

var Broadcaster = function(listener) {
  this.doSomething = function() {
    // do something...
    listener.doSomething(info);
  };
};

This creates strong coupling between the broadcaster and the interface for the listener. This pattern is not as limiting as it may first appear though. A common approach to extending the usefulness of this pattern is to replace the basic listener with a broadcasting adapter. The broadcasting adapter appears the same as the basic listener in that it carries the same doSomething interface but implements an event architecture to pass this call on to other objects. This keeps the broadcaster very simple and externalizes anything to do with events at the cost of an additional class.

3. Both are independent

Your first example is a good example of releasing this coupling the cost is an additional class. The intermediate class acts as a bridge or adapter between the two classes so that neither is dependent on each other's information. The event signature is hidden from the listener and the method signature for the listener is hidden from the broadcaster. Often this level of decoupling is unnecessary but is an important tool to be aware of where it is important to keep two classes isolated from each other.

var Bridge = function(listener, broadcaster) {
  broadcaster.on("event", function(info) {
    // if necessary translate the arguments as well
    listener.doSomething(info);
  });
};
like image 152
Stuart Wakefield Avatar answered Oct 13 '22 15:10

Stuart Wakefield


Edit

Per your comment, I'm not sure what you find unreadable about this, but you can try using Stream objects and the elegant Stream#pipe method.

The Streams

var Stream = require('stream').Stream;
var util = require('util');

var Sender = function() {
  this.readable = true;
  this.saySomething = function(message) {
    this.emit('data', message);
  };
};
util.inherits(Sender, Stream);

var Receiver = function() {
  this.writable = true;
  this.write = function(message) {
    console.log('received: ' + message);
  }
};
util.inherits(Receiver, Stream);

Usage

// instance of each
var s = new Sender();
var r = new Receiver();

// connect using pipe!
s.pipe(r);

// an event
s.saySomething('hello world'); //=> received: hello world

I particularly like using Stream objects. The pipe method is quite handy :) Each stream has a single input and output. I like them because it encourages you to build small, functional components that are responsible for a specific task. Then, you can chain/reuse them however you see fit.


Original post

You'll want to use EventEmitter

Here's a quick example

var events = require('events');
var util = require('util');

var SampleEmitter = function() {
    events.EventEmitter.call(this);

    this.sayHello = function() {
      this.emit('hello', 'hello world');
    };
};
util.inherits(SampleEmitter, events.EventEmitter);

var SampleListener = function(emitter) {
    emitter.on('hello', function(data) {
        console.log(data);
    });
};

var se = new SampleEmitter();
var sl = new SampleListener(se);

se.sayHello(); //=> 'hello world'
like image 45
maček Avatar answered Oct 13 '22 14:10

maček