2

I'm trying to understand the best way to use events (i.e. Node's EventEmitter) and promises in the same system. I love the idea of decoupled components that communicate loosely through some sort of central event hub. When one component has some information that might be relevant to another component it can emit it on the hub. The components generating the information need to know nothing about the components that are consuming the information.

But sometimes the interaction is more of a request/response behaviour for which promises seem more ideal. I am wondering if there is a way to unify these methods of communication.

For example, I could have a component that has a encrypted message and needs it decrypted. I want a loose coupling between the component that is doing something with the message and the component handing the message decryption. Using promises provide a nice interaction for request/response but represents tight coupling. For example:

message = ... some encrypted message ...
decrypting_lib.decrypt(message).then(function(decrypted_message) {
  ... do something with decrypted_message ...
})

But as you can see I have a tight coupling by referencing the decryption library. With events I can remove that coupling but the code seems more awkward:

message = ... some encrypted message ...
callback = function(decrypted_message) {
  if( message.id != decrypted_message.id ) return;
  hub.removeListener('message:decrypted', callback);
  ... do something with decrypted_message ...
}
hub.on('message:decrypted', callback);
hub.emit('message:decrypt', message);

In the second example, we emit a message to ask for the message to be decrypted while listening for it to be decrypted. Of course since other messages could be decrypted that may not be the message we are looking for we need to check for that. We also need to stop listening once we found the message we are interested in.

As you can see we now are decoupled from whatever is doing the decryption but our code is MUCH more complicated. My ideal API would allow the EventEmitter-style interface for when our code doesn't require a request/response type interaction. But when it does have a request/response interaction it would look something like:

hub.on('message:decrypt', message).then(function(decrypted_message) {
  ... do something with decrypted_message ...
});

In this example the decryption library would decrypt the message then emit an event to indicate a message was decrypted. This would notify all parties that are interested in any message being decrypted. But it would also call the promise that was passed in which started the process.

Is there any library out there that does something like this. Or perhaps someone more experienced with JS applications could provide a better way to do what I am trying to do.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Eric Anderson
  • 3,692
  • 4
  • 31
  • 34
  • good related question: [One time event handling using promises?](http://stackoverflow.com/q/23115272/1048572) – Bergi Oct 19 '14 at 16:20
  • Good question. You can't unify them completely - see https://github.com/kriskowal/gtor . – Benjamin Gruenbaum Oct 19 '14 at 16:30
  • Your `on('message:decrypt')` example is problematic because a promise represents a one time event and the event does not have that guarantee. Are you looking for ways to show progression? (It's hard to unify that too) – Benjamin Gruenbaum Oct 19 '14 at 16:32
  • 2
    Your idea of the API also doesn't compose very well - for the same reason promise progression is being removed from libraries - what would Promise.all do in this case? Also - loose coupling isn't good when it comes at the expense of having a clear API. What part of the coupling is bugging you here? – Benjamin Gruenbaum Oct 19 '14 at 16:37

1 Answers1

0

If you want to have things decoupled with events, and on the other side want to have convenient promise based interface. The only way I think it can be achieved, is via introduction of other utility that would bind both e.g.

hub.registerForDecryption = function (message) {
  hub.emit('message:decrypt', message);
  return new Promise(function (resolve, reject) {
    hub.on('message:decrypted', function (decrypted) {
      if (message.id !== decrypted.id) return;
      resolve(decrypted);
    });
  });
};
...
hub.registerForDecryption(message).then(function (decrypted) {
  .. do something with decrypted_message ...
});
Mariusz Nowak
  • 32,050
  • 5
  • 35
  • 37