Event oriented programming with JavaScript

Event oriented programming promotes low coupling but not necessarily high cohesion. Always consider the Single Responsibility Principle while designing collaboration through events. I see three primary ways of collaboration:

One to One

A single object communicates with other (a collaborator). This can be done with dependency injection the same way we do it in Java or any other language (and you don't need Spring for that!). JavaScript provides another mechanism for one2one communication, the DOM Level 0 traditional way of event handling. A very useful and powerful way of adding hooks/ports to plug new behavior in, still promoting low coupling. If you need to change the collaboration in the future, from one2one towards one2many, it can be done easily. In some cases I see this as a more decoupled way of dependency injection.

  1.  
  2. function Car(){
  3. this.go = function(){
  4. /* some calculation over here */
  5. this.onLowFuel();
  6. };
  7. this.onLowFuel = function(){/* event */}
  8. }
  9. function RacingTeam(){
  10. function getReadyToFuel = function(){/* whatever */};
  11.  
  12. this.addCarToRace = function(car){
  13. car.onLowFuel = function(){
  14. console.log('low fuel!', this);
  15. getReadyToFuel();
  16. }
  17. };
  18. }
  19. var car = new Car();
  20. var team = new RacingTeam();
  21. team.addCarToRace(car);
  22.  

As you might realize I know nothing about car races 🙂 but I hope you understand the code. When implementing traditional dependency injection, the car knows it depends on a team, but it doesn't know the particular implementation of the team. With the DOM level-0 traditional way, the car doesn't even know it will be used in a team. All it knows is that it has to inform someone else about its fuel level when it gets low. However, the team's got to know about the car. Still the team should not know about the particular implementation of the car.
To test-drive this behavior, you probably need two steps. First one, make sure the car triggers the event and second, make sure the team handles the event. I wrote the two tests in a previous post. DOM level-0 traditional is the main mechanism I use for one to one collaboration, it's got however some pitfalls worth considering.

Pitfalls
  • You might not know some object is already handling the event: the handling mechanism is simple, we are just replacing the original function in the object triggering the event. If we try handle the same event twice, there will be only one handler, the last one. I always use the "on" prefix for those methods that trigger events as a convention to know it is an event with a single handler.
  • You might make mistakes with the "this" keyword: in the code above, what do you think "this" is when we invoke console.log? It's the car object, not the team. It's a problem with a very easy solution though. I usually have a reference to the handler doing "var self = this;". Some people prefer the "bind" method to do the same.

One to Many

For one to many collaboration there is the Observer pattern. jQuery implements the observer pattern to expose events such as "click":

  1.  
  2. $('#closeButton').click(function(){
  3. /* the event handler 1 */
  4. console.log('handler1');
  5. });
  6. $('#closeButton').click(function(){
  7. /* the event handler 2 */
  8. console.log('handler2);
  9. });
  10.  

When the button is clicked, both handlers are executed. When I implement the observer pattern in my objects, I prefer to make it explictly:

  1.  
  2. objectTriggeringTheEvents.addSubscriber('eventName', handler1);
  3. objectTriggeringTheEvents.addSubscriber('eventName', handler2);
  4.  

The "addSubscriber" makes clear to me, that I can have several subscribers. This is specially important to distinguish from one2one collaboration.
As an exercise I leave out to you the implementation of the observer pattern with TDD.
I use one2many collaboration when I really need the object to be observed by several objects, which happens to me, less often than one2one collaboration.

Many to Many

Different objects might trigger the same event and there might be a bunch of other objects subscribing that event. This is the case of a many2many collaboration. Or, I might not be interested in the source of the event, I might just want to handle it. This usually happens at the infrastructure level. For example I use it to know when the window is being closed. I don't care who is responsible for realizing the window is being closed but I do want to know it in several parts of my application to shutdown properly. So I create an object which subscribes to "window.onbeforeunload" to be aware of window closing and in turn, emits the event through a "bus" where subscribers are listening.
This is the less coupled way of interaction between objects, but it comes with counterparts: code gets harder to follow. This is the publish/subscribe pattern. My advise is to use this pattern only when strictly necessary and not as a the default mechanism for event oriented programming.
There are several open source libraries implementing this pattern. As an exercise, try to develop it from scratch, test-first. This is the code I ended up with while implementing my own pub/sub:

  1.  
  2. EventBus = function(){
  3. var subscribersInfo = [];
  4.  
  5. this.addSubscriber = function(callback){
  6. var eventNames = [].slice.call(arguments).slice(1);
  7. subscribersInfo.push({
  8. subscriber: callback, eventNames: eventNames});
  9. };
  10.  
  11. this.emit = function(eventName, eventArgs){
  12. for(var i = 0, len = subscribersInfo.length; i < len; i++){
  13. var info = subscribersInfo[i];
  14. for (var j = 0, lenj = info.eventNames.length; j < lenj; j++){
  15. if (info.eventNames[j] == eventName)
  16. info.subscriber(eventName, eventArgs);
  17. }
  18. };
  19. }
  20. };
  21.  
Enjoyed reading this post?
Subscribe to the RSS feed and have all new posts delivered straight to you.
  • Pingback: Test driving an event driven design « El blog de Carlos Ble()

  • http://twitter.com/axelhzf Axel Hernández

    Hola Carlos

    Muy bueno el post. Suelo trabajar con backbone y hago uso intensivo de eventos para conectar los distintos componentes de la aplicación. Un problema con el que me he encontrado es que cuando crece el numero de eventos que intervienen en una vista en concreto se hace complicado seguir el flujo de ejecución. Una de las prácticas (no se si buena o mala) que estoy siguiendo es intentar centralizar en un punto del código la conexión de los eventos. De forma que viendo ese trozo de código pueda identificar las relaciones que existen entre los elementos.

    Otro problema es cómo documentar de alguna manera los eventos que puede lanzar un objeto. Es muy duro tener que revisar el código de la clase para encontrar algún método que haga un emit(“evento”).

    ¿Te has enfrentado a estos problemas? ¿Cómo los resuelves?

    A veces mientras estoy depurando suelo escribir un wrapper de la función emit para que me muestre un log de los eventos que se están disparando. Cuando tengo varios eventos encadenados me ayuda a detectar problemas.

  • carlosble

    Hola Axel,
    Gracias por el feedback. Siempre que veo ejemplos/tutoriales de backbone pienso en eso. Se abusa del modelo pub/sub y efectivamente lleva a codigo dificil de seguir. El problema es que las responsabilidades estan disgregadas por todas partes. Cada artefacto debe tener una unica responsabilidad, no puede ser que tenga varias ni tampoco que su responsabilidad este dispersa, repartida en varios artefactos. Asi que efectivamente mis eventos estan gestionados en un solo sitio. Uso el patrón Vista Pasiva para ello. Mis vistas no escuchan eventos, solo los lanzan cuando tienen que ver con la UI.

    Personalmente no uso ni Backbone ni Ember, ni ningun otro framework para mis aplicaciones. Solo jQuery como API de acceso al DOM. En realidad sí uso Backbone, pero solo para disponer del router para la gestion de urls, nada mas.

    Cuando haces TDD, practicamente nunca tienes que depurar. Yo lo hago muy rara vez. Cuando todo va bien mis tests están muy centrados en la parte del codigo que estoy trabajando y me dicen exactamente lo que tengo que hacer o dónde hay algun problema. Es una diferencia abismal.

    Los eventos los organizo dentro de un namespace para no tener strings literales repartidos en el codigo: Appnamespace.Events.windowClosing.

    En la lista de AgileCanarias he ofrecido la posibilidad de que hagamos mi taller en Tenerife este mes.

    Saludetes 🙂

  • Pingback: Unit testing JavaScript with Promises and Jasmine « El blog de Carlos Ble()