7

I haven't been able to find the appropriate terminology to search for content on the web related to what I'm asking, so I'm hoping someone on here can at least point me in the right direction.

I'm a fairly senior JavaScript developer, but I have a secret -- I don't like event-driven programming (aside from user-triggered event binding I mean). Something about it has never sat well with me, it's the feeling of a "disconnect" in the logic flow.

In the simple example of some module invoking arbitrary numbers of views, an event-driven friend would create event listeners for elements those views could have and keep any reference to the parent module out of the view code.

I, on the other hand, would pass reference to the parent module in to the views so that I could directly trigger the module's methods.

I go for the second approach because I know exactly what is going to happen and what method I'm triggering at all points in the code. In the event-driven case I'm separating the assignment of an event response from the element that would trigger it, putting things in to the 'listening ether'.

I know responsible event-driven programming should avoid any risks like double-binding, etc., but even when that care is taken I still see it all as a bit of a black-box ripe for debugging nightmares. While passing references may be a bit more cumbersome in the code, at least it's clear what things are going to happen and when.

Can anyone identify the pros and cons of these two methods, and in particular why JS seems to be pushing in general toward the event-driven stuff?

4
  • what is the question?
    – gnat
    Commented Dec 16, 2013 at 20:04
  • How do you respond to user interactions without events? Commented Dec 16, 2013 at 20:23
  • I said in my second paragraph -- user events aside. So the user clicks a button, but if that button needs to run a method in some other module, should that module be listening for the button or should the button have reference to the module scope to trigger the method directly?
    – Dan
    Commented Dec 16, 2013 at 20:26
  • 5
    @Dan: 2 people have put in the effort of interpreting your question and we both have, IMO, provided a complete an answer as possible given the question. If you don't accept either one of them, then could you please explain why? Perhaps clarify the actual question some more, using code examples? Commented Dec 18, 2013 at 8:23

3 Answers 3

10

Not sure what you mean with reference driven programming. From what I gather, you're wondering what the advantages of event-driven programming are as opposed to writing code, and then using a bunch of branches to determine when to call a given method.

Before I set off, allow me to be pedantic and point out that: a module can't listen for any event, nor can a button have a reference to a module. The button is part of the DOM, the module is JS. Both live in separate universes, and never the twain shall meet.
You can have the event loop pick up on certain events, and then invoke a given function object (either stand-alone, anonymous, or a function object that is part of a module) to handle that event, or you can mix JS into your markup, using certain html attributes (onclick="" and the like).
The latter method is generally said to be outdated, messy, and therefore bad practice. In this day and age, where OOP-buzzwords have found there way into the daily vocab of the average middle-manager, mixing JS in with markup isn't what I'd call separation of concern...

I see it like this:
JavaScript was originally intended to run client side. It's not been considered a valid server-side language for that long. It's small, light and portable, and was meant to make it easy to "liven up" your website. Making the UI responsive, move stuff around and, most importantly, load content dynamically using AJAX.
When talking about AJAX, I think you'll agree, event-driven programming is the better option: you request content, based on user input (clicks links, scrolls the page down etc...), and as you probably know: an ajax response is dealt with using an event handler (xhr.onreadystatechange = function).

Since then, we've come a long way, but some things have remained the same: JS is, in essence, a functional language, which implies closures, lambda's and callback functions to be used all over the place. The easiest way to make functional code work is to set all the functions up, and then let a change in the state (aka event) call one or more of these functions. Basically: functional languages feel at home in an event driven universe.
The expressive power of these constructs is huge, and so it stands to reason that with every new trick, technology or feature that gets added, the people developing the JS engines will tend to implement events to better support these new bells and whistles. Couple that to the fact that the main libs/toolkits will probably provide an API that requires you to pass around functions, and you end up with a bunch of developers who, when confronted with a problem, will look at it as a series of events, that they can tap into to respond accordingly.
So, I guess you might say that JS was designed to be event-driven to a rather large extent, and that the people who use it are "trained" if you will to think accordingly.

Another thing to consider is: how the engines work. V8, for example, is great at idling. If you run node.js, you'll see that it's perfectly happy, consuming very little resources, just sitting there, doing nothing. Once it receives a request (which is an event in a way), it'll wake up and set to work.
Event driven code does reflect this:

http.createServer(function(){}).listen();

This script doesn't do anything, until the server "has to be there", then the function passed to createServer is invoked, and, you could say, becomes the server.

Back to the client-side:
You say you see the point of event binding with DOM elements, and responding to the user's actions. Well, that's what client-side JS does. Its main purpouse was (and is) to augment the user experience. Whatever code gets executed client side, should be triggered by an event.
The event could be the load event, or a click or change event... it doesn't matter. If the user isn't doing anything, JS shouldn't be busy. Some browsers kill a script if it's busy for too long anyway.
You ought to realize that picking up on changes, without using the event loop JS already has to offer, leaves you no real alternative than to write a busy loop: JS isn't meant to be a system language, so you could end up with: for(;;) or while(1) loops. If you then were to have a single event handler, owing to JS being single threaded, that handler can't ever get called, because the JS thread is always busy, claiming the CPU like there's no tomorrow.

I can't see how else you could set about your business, to be honest. Worker's? No, because they communicate only through events. setTimout and setInterval? Not really: it's a right hasstle to sort out the scope, it's error prone and expensive. Besides, an event probably translates to an interrupt at some lower level, as does an interval, or a timeout. What those do is, essentially the same thing as events, only: they're obtrusive, because they weren't initiated by the user, and they could block the UI.
The only "alternative" I can think of, and reading your question and comments again is what you're actually thinking of, is something along the following lines. The first is the reference driven example, the second is a quick translation of the same basic concept to event-driven programming:

var btnMod = (function(w)
{
    var nextOn = false,
    stopEvent = function(e)
    {
        e.returnValue = false;
        e.cancelBubble = true;
        if (e.preventDefault)
        {
            e.preventDefault();
            e.stopPropagation();
        }
        return false;
    },
    module = {};
    module.switch = function(e,elem)
    {
        e = e || w.event;
        //do stuff
        nextOn = !nextOn;//toggle bool
    };
    module.next = function(e,elem)
    {
        e = e || w.event;
        //do stuff
        if (nextOn) location.href = 'next-url';//for example
        else return stopEvent(e);
    };
    return module;
}(window));

then either have markup like below, or bind the module methods to certain elements

<li onclick="module.switch(event, this)">on/off</li>
<li onclick="module.next(event,this)">next</li>

As opposed to doing something like

var btnMod = (function(w)
{
    var stopEvent = function(e)
    {
        e.returnValue = false;
        e.cancelBubble = true;
        if (e.preventDefault)
        {
            e.preventDefault();
            e.stopPropagation();
        }
        return false;
    },
    module = {};
    module.switch = function(e,elem)
    {
        e = e || w.event;
        w.removeEventListener('click', module.switch, false);
        w.addEventListener('click', module.next, false);
    };
    module.next = function(e,elem)
    {
        e = e || w.event;
        w.removeEventListener('click', module.next, false);//unbind to disable
        //do stuff
    };
    if (!nextOn) w.addEventListener('click', module.switch,false);
    else w.addEventListener('click', module.next, false);
}(window));

Well, quite apart from the fact that event-driven code is lighter (only one listener is bound at a time), and less error prone (if the module.next listener is bound, the switch listener isn't), it also doesn't require JS to mixed in with JS. Given some more effort, it can also be written a lot shorter than the snippets I've posted here, and it's not littered with tons of if's and else's. it uses functions, which is what you do in a functional language

Bottom line:
The advantages of event-driven JS are simple, IMO: JS was intended to be used on the web, client-side. It was meant to enhance the user experience by making static pages responsive, and fetching content on-the-fly. All through events. Coupled with the functional paradigm, that makes for a rather expressive little language. To use it in any other way means using it in a way it wasn't designed to be used (excel).

If you don't use a hammer to fasten a screw if you have a screwdriver lying right next to it.
Likewise: you don't work around events, if there is an event readily available.

In the end, though, what's in a name?. If ever you write a standalone program in C, using the GTK+ lib, you'll use "events", too. C# is popular nowadays, guess what, it wont take many tutorials before you see the word "event" crop up. You want to react on some changes in state or user input? That's what we happen to call an event. Lastly: as svidgen pointed out: JS engines already have an event system written out. Good programmers are lazy: they don't go and write their own systems, if they can find an existing one that allows them to do what they need to do. Sure, sometimes you may find the existing systems lacking in some respect, and curse the people making it for not seeing that there might be a need for whatever you happen to need at that time, but that's life.
Personally, I'd say: if you don't like event-driven programming, you don't really like JS(?)

6

Not 100% sure I know what you're asking. But, I get the sense you're at a point where event-driven programming doesn't feel "real" enough -- or like you're at the mercy of someone else's event system. Or like your application isn't really "doing" anything. Or like it's just a bunch of disparate methods that you're feeding another application.

So. Suppose a user-facing application creates and environment and waits for stuff to happen, which is what most applications do. There are two ways to receive those "happenings."

Periodically poll for evidence that something has happened.

Pros:

  • You can respond to happenings that aren't "events" in someone else's event-model.
  • The application is not interrupted when it's in the middle of something important. (Not generally relevant concern in JavaScript, because events are queued until the pipe is clear.)
  • The application retains more precise control over what's running; it has the flexibility to completely ignore input, only check for input every N cycles or seconds, etc.
  • It may give you a [mistaken] sense of satisfaction that you're writing more real, more thorough code. You're not relying on some silly, mysterious event system. You're doing all the work, dammit!

Cons:

  • Application is iteratively checking for event flags, often needlessly.
  • CPU usage is high and potentially running inefficiently due to frequent context switches, increasing likelihood of thrashing.
  • You likely end up writing and event-system and event-driven code anyway ...

E.g. ...

while (true) {
  var flags;
  if (flags = checkMouseFlags()) {
    doMouseInput(flags);
  }
  if (flags = checkKeyboardFlags()) {
    doKeyBoardInput(flags);
  }
}

function doMouseInput() {
  // stop lying to yourself. this is an event.
}

function doKeyboardInput() {
  // this is also an event.
}

... Alternatively:

Let the host invoke the code when something happens.

Pros:

  • It's an extension of what happens at lower levels. Unless you're building the hardware, you're ultimately writing event handlers: The CPU is sleeping. It's woken up when something happens. The CPU interrupts the OS to say, "hey, something happened." The OS determines that Chrome should receive the event. The OS invokes Chrome's event-handler. Chrome sets some flags and stuff and invokes your event handlers.
  • You're not needlessly hogging CPU cycles and introducing context switches.
  • No need to (re)write and event-system [under the mistaken notion that it's not an event-system].
  • More reflective of reality, easier to wrap your head around, and easier maintain (once you embrace the model) (Even though you probably don't stop background thinking when you're in a conversation, you also don't "poll" for input. Input from your ears interrupts your thought-process and your respondToStupidRemark handler is triggered.)

Cons:

  • Puts you at the mercy of someone else's event-system.
  • Small chance you'll have to do some perverse looking stuff or poll for "events" anyway to respond to things that aren't "events" in your host's event-system.
  • Weird sense that your application isn't doing anything ...
  • ... Or maybe a weird sense that it's not even an application; just a bunch of tangentially related event-handlers.

Notable: In other languages, and with the advent of worker processes in JavaScript, it's not an either-or paradigm. You can have a background process butchering the CPU and use the existing event-system to do as much or as little with each event as you like.

So, why is JavaScript event-driven?

Because it's reflective of the modeled reality, easy to think about, gentle on the CPU, and it simply extends the host environment to the script.

1
  • +1 for the stop lying to yourself, this is an event comment, and because your answer is, of course, correct Commented Dec 17, 2013 at 16:21
3

The Big Differences...

While the pros & cons above are valid concerns, I don't care [too] much about them on a higher scale.

Here are the pros & cons which determine whether I write in one paradigm or the other:

Reference-Based

Usually, this approach will lead you down paths you're not aware of. You will likely be coupling intensively -- and it will be hard to save yourself before realize you're damned, or you may see coupling so strong that your code smells eventuate into code hauntings. This aside, it is easier to not feel like you're dispatching events off into the "listener ether". However, you may feel comfortable that you have that object at execution -- and if you don't, you see it plainly & clearly -- but, with great coupling comes great inflexibility; namely, Circular Dependencies, which happens to relate explicitly to Coupling.

While employing this strategy, you better get creative with and mind your P's & Q's for the following:

  • Proxy Patterns,
  • Adapters,
  • Command Patterns,
  • DIP (Dependency Inversion Principle for Circular Dependencies),
  • The rest of the SOLID Principles,
  • Coherence,
  • Content Coupling,
  • Common Coupling,
  • External Coupling,
  • Control Coupling,
  • Stamp Coupling,
  • Data Coupling,

EDA's (Event-Driven Architectures)

Event-Driven Architectures' pain-point resides in that fact that its a bit tougher to figure out what's going on, just by looking at the events. Furthermore, with EDA's you have to make sure you listen before you speak, or rather subscribe before you publish. Because of this, you may end up implementing more onReady channels -- but this is tantamount to employing an init() method, which you should do anyway.

While employing an EDA, you can get as creative as you want (without breaking depdendency-networks) and better mind you P's & Q's regarding:

  • Message Coupling,

... That's about it. Everything else becomes magnitudes easier while writing in this paradigm... Coherence, SOLID, ... Patterns become an asset instead of a necessity. You can even take an existing Class and apply a Pattern [such as Decorator, Prototype, Adapter, etc] to it and make it Event-Driven by simply wrapping it. Other creative opportunities come along such as how & who decides to debounce an input or throttle some other behavior.

The main benefit of EDA's regards Coupling and making modules Liftable and Droppable. However, if you haven't seen the degrees of coupling I have -- and are not convinced that EDA's are worth it -- you can drastically obviate some of the aforementioned pain-points via the Mediator Pattern:

Mediators & Directors

[RESEARCH THE MEDIATOR PATTERN]

Reference-Driven

If you're uneasy about firing off events into the listener ether, you can employ a reference-driven mediator which still "uses" Message-Coupling. You just provide both publish() & subscribe() methods on your mediator. This interface is simply a Facade Pattern to take advantage of the GoF Rules of Thumb: Encapsulate what varies and Program to an interface, not an implementation. Here's a rough design:

var Director = function Director(dependencies) {
    var thus = this;
    var d = dependencies;

    function noop() {}

    (function PubSub() {
        function sub(channel, handler) { this[channel] = handler; return this; }
        function pub(channel, data) {
            var args = Array.prototype.slice.call(arguments, 1);
            this[channel] && this[channel].apply(null, args);
            return this;
        }
    }).apply(this);

    this['data://found/user/settings'] = function () {
        this['do://something/else']();
    };

    return this;
};

var SomeController = function SomeController(director) {

    function init(locals) {
        director.subscribe('data://found/user/settings', dataFoundHandler.bind(this));

        return this;
    }

    function foundHandler(data) {
        this.data = data;
    }

    this.init = init;
};

This is just a rough example to give you the idea, but once you've studied the Mediator Pattern then all of this should make perfect sense.

Event-Driven

I won't really dive into Event-Driven Mediation as its basically the exact same idea -- you just have a framework [your own, if necessary] to aid in the interface; you even get the benefit of multiple handlers for a given Channel. All you have to do is wrap your dispatches in a handler for the channel which triggered the given mediated event.

Summary

Both do have advantages, as described above, and the use of Mediators and Event-Driven strategies may make it tougher to know how your modules are interacting -- but in the end, Coupling causes all sorts of headaches and once you dive into the EDA paradigm you'll get used to it. Also, the use of a Mediator, alone, will provide a nice docket to see how your modules are interacting, and with tools like Capo, you have an even clearer picture of these interactions.

Cheers!


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.