67

I have been passing callbacks or just triggering the functions from other function in my programs to make things happen once tasks complete. When something finishes, I trigger the function directly:

var ground = 'clean';

function shovelSnow(){
    console.log("Cleaning Snow");
    ground = 'clean';
}

function makeItSnow(){
    console.log("It's snowing");
    ground = 'snowy';
    shovelSnow();
}

But I've read about many different strategies in programming, and one that I understand to be powerful, but have not yet practiced, is event-based (I think a method I read about was called "pub-sub"):

var ground = 'clean';

function shovelSnow(){
    console.log("Cleaning Snow");
    ground = 'clean';
}

function makeItSnow(){
    console.log("It's snowing");
    ground = 'snowy';
    $(document).trigger('snow');
}

$(document).bind('snow', shovelSnow);

I'd like to understand the objective strengths and weaknesses of event-based programming, vs just calling all of your functions from within other functions. In which programming situations does event-based programming make sense to use?

2
  • 2
    As an aside, you can just use $(document).bind('snow', shovelShow). No need to wrap it in an anonymous function. Dec 31, 2014 at 15:29
  • 4
    You might also be interested in learning about "reactive programming", which has a lot in common with event-driven programming. Jan 1, 2015 at 17:46

5 Answers 5

77

An event is a notification describing an occurrence from the recent past.

A typical implementation of a event-driven system utilises an event dispatcher and handler functions (or subscribers). The dispatcher provides an API to wire handlers up to events (jQuery's bind), and a method to publish an event to its subscribers (trigger in jQuery). When you're talking about IO or UI events, there's also usually an event loop, which detects new events such as mouse-clicks and passes them to the dispatcher. In JS-land, the dispatcher and event loop are provided by the browser.

For code that interacts directly with the user - responding to keypresses and clicks - event-driven programming (or a variation thereof, such as functional reactive programming) is almost unavoidable. You, the programmer, have no idea when or where the user is going to click, so it's down to the GUI framework or browser to detect the user's action in its event loop and notify your code. This type of infrastructure is also used in networking applications (cf NodeJS).

Your example, wherein you raise an event in your code rather than calling a function directly has some more interesting tradeoffs, which I will discuss below. The main difference is that the publisher of an event (makeItSnow) does not specify the receiver of the call; that's wired up elsewhere (in the call to bind in your example). This is called fire-and-forget: makeItSnow announces to the world that it's snowing, but it doesn't care who's listening, what happens next, or when it happens - it simply broadcasts the message and dusts off its hands.


So the event-driven approach decouples the sender of the message from the receiver. One advantage this affords you is that a given event may have multiple handlers. You could bind a gritRoads function to your snow event without affecting the existing shovelSnow handler. You have flexibility in the way your application is wired up; to turn off a behaviour you just need to remove the bind call rather than go hunting through the code to find all the instances of the behaviour.

Another advantage of event-driven programming is that it gives you somewhere to put cross-cutting concerns. The event dispatcher plays the role of Mediator, and some libraries (such as Brighter) utilise a pipeline so that you can easily plug-in generic requirements such as logging or quality-of-service.

Full disclosure: Brighter is developed at Huddle, where I work.

A third advantage of decoupling the sender of an event from the receiver is that it gives you flexibility in when you handle the event. You could process each type of event on its own thread (if your event dispatcher supports it), or you can put raised events onto a message broker such as RabbitMQ and handle them with an asynchronous process or even process them in bulk overnight. The receiver of the event could be in a separate process or on a separate machine. You don't have to change the code which raises the event to do this! This is the Big Idea behind "microservice" architectures: autonomous services communicate using events, with messaging middleware as the backbone of the application.

For a rather different example of event-driven style, look to domain-driven design, where domain events are used to help keep aggregates separate. For example, consider an online store which recommends products based on your purchase history. A Customer needs to have its purchase history updated when a ShoppingCart is paid for. The ShoppingCart aggregate might notify the Customer by raising a CheckoutCompleted event; the Customer would get updated in a separate transaction in response to the event.


The main downside of this event-driven model is indirection. It's now harder to find the code which handles the event because you can't just navigate to it using your IDE; you have to figure out where the event is bound in the configuration and hope that you've found all the handlers. There's more stuff to keep in your head at any one time. Code style conventions can help here (for example, putting all the calls to bind in one file). For the sake of your sanity, it's important to only use one event dispatcher and to use it consistently.

Another disadvantage is that it's difficult to refactor events. If you need to change the format of an event you also need to change all the receivers. This is exacerbated when the subscribers of an event are on different machines, because now you need to synchronise software releases!

In certain circumstances, performance may be a concern. When processing a message, the dispatcher has to:

  1. Look up the correct handlers in some data structure.
  2. Build a message processing pipeline for each handler. This may involve a bunch of memory allocations.
  3. Dynamically call the handlers (possibly using reflection if the language requires it).

This is certainly slower than a regular function call, which only involves pushing a new frame on the stack. However, the flexibility that an event-driven architecture affords you makes it much easier to isolate and optimise slow code. Having the ability to submit work to an asynchronous processor is a big win here, as it allows you to serve a request immediately while the hard work is dealt with in the background. In any case, if you're interacting with the DB or drawing stuff on the screen then the costs of IO will totally swamp the costs of processing a message. It's a case of avoiding premature optimisation.


In summary, events are a great way to build loosely coupled software, but they are not without cost. It would be a mistake, for example, to replace every function call in your application with an event. Use events to make meaningful architectural divisions.

8
  • 2
    This answer says the same as 5377's answer which I selected as correct; I'm changing my selection to mark this one because it elaborates further.
    – J.Todd
    Dec 31, 2014 at 22:34
  • 1
    Is speed a significant disadvantage of event-driven code? It seems like it could be, but I don't quite know. Jan 2, 2015 at 5:06
  • 1
    @raptortech97 it certainly can be. For code that needs to be particularly fast, you would probably want to avoid sending events in an inner loop; fortunately, in such situations its usually well defined what you need to do, so you don't need the extra flexibly of events (or publish/subscribe or observers, which are equivalent mechanisms with different terminology).
    – Jules
    Jan 2, 2015 at 12:58
  • 2
    Also note that there are some languages (e.g. Erlang) built around the actor model where everything is messages (events). In this case the compiler can decide whether to implement the messages/events as direct function calls or as communication.
    – Brendan
    Jan 12, 2015 at 3:15
  • 1
    For "performance" I think we need to distinguish between single-threaded performance and scalability. Messages/events can be worse for single-threaded performance (but can be converted into function calls for zero additional cost and be no worse), and for scalability it's superior in virtually every way (e.g. likely to result in massive performance improvements on modern multi-CPU and future "many-CPU" systems).
    – Brendan
    Jan 12, 2015 at 3:16
27

Event-based programming is used when the program does not control the sequence of events that it performs. Instead, program flow is directed by an outside process such as a user (e.g. GUI), another system (e.g. client/server), or another process (e.g. RPC).

For example, a batch processing script knows what it needs to do so it just does it. It is not event-based.

A word processor sits there and waits for the user to start typing. Keypresses are events that trigger functionality to update the internal document buffer. The program cannot know what you want to type, so it must be event-driven.

Most GUI programs are event-driven because they are built around user interaction. However, event-based programs are not limited to GUIs, that is simply the most familiar example to most people. Web servers wait for clients to connect and follow a similar idiom. Background processes on your computer may respond to events too. For example, an on-demand virus scanner may receive an event from the OS regarding a newly created or updated file, then scan that file for viruses.

18

In an Event-based application the concept of Event Listeners will give you the ability to write even more Loosely Coupled applications.

For example a third-party module or plug-in can delete a record from the database and then trigger the receordDeleted event and leave the rest to the event listeners to do their job. Everything will work fine, even though the triggering module doesn't even know who's listening for this particular event or what should happen next.

7

A simple analogy I wanted to add that helped me:

Think of the components (or objects) of your application as a big group of Facebook friends.

When one of your friends wants to tell you something, they can either call you directly or post it to their Facebook wall. When they post it to their Facebook, then anyone could see it and react to it, but a lot of people don't. Sometimes it's something important that people will likely need to react to it, like "We're having a baby!" or "So-and-so band is doing a surprise concert at the Drunkin' Clam bar!". In the last case, then the rest of the friends will likely need to react to it, especially if they are interested in that band.

If your friend wants to keep a secret between you and them, they probably wouldn't post it to their Facebook wall, they would call you directly and tell you. Picture a scenario where you tell a girl you like that you would like to meet her at a restaurant for a date. Instead of calling her directly and asking her, you post it to your Facebook wall for all your friends to see. This works, but if you have a jealous ex, then she could see that and appear at the restaurant to ruin your day.

When deciding whether or not to build in event listeners for implementing something, think about this analogy. Does this component need to put their business out there for anyone to see? Or do they need to call someone directly? Things can get messy pretty easily so be careful.

0

This following analogy might help you to understand event driven I/O programming by drawing a parallel to waiting line at Doctor's Reception desk.

Blocking I/O is like, if you are standing in the queue, receptionist asks a guy in front of you to fill in the form and she waits till he finishes. You have to wait for your turn till the guy finishes his form, this is blocking.

If single guy takes 3 mins to fill in, the 10th guy have to wait till 30 minutes. Now to reduce this 10th guys wait time, solution would be, increasing number of receptionist's, which is costly. This is what happens in traditional web servers. If you request for a user info, subsequent request by other users should wait till the current operation, fetching from Database, is completed. This increases the "time to response" of the 10th request and it increase exponentially for nth user. To avoid this traditional web servers creates thread (equivalent to increasing number of receptionists) for every single request, ie., basically it creates a copy of the server for each request which is costly interms of CPU consumption since every request will need a Operating systems thread. To scale up the app, you would have to throw lots of computation power at the app.

Event Driven: The other approach to scale up queue's "response time" is to go for event driven approach, where guy's in the queue will be handed over the form, asked to fill in and come back on completion. Hence receptionist can always take request. This is exactly what javascript has been doing since from it's inception. In browser, javascript would respond to user click event, scroll, swipe or database fetch and so on. This is possible in javascript inherently, because javascript treats functions as first class objects and they can be passed as a parameters to other functions (called callbacks), and can be called on completion of particular task. This is what exactly node.js does on the server. You can find more info about event driven programming and blocking i/o, in the context of node here

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