Skip to content

Events in the Event Loop

The other day I wrote about the event loop in node.js. In particular, there is a nice trick of ceding control to the event loop using process.nextTick()

But a question arises. How are events related to the event loop? Amusingly, they are completely separate.

Naively, I thought that when an event is emitted, the resulting actions are queued up in the event loop. Not so. Events and their handlers are acted on immediately. And this is a good thing.

When are events emitted?

How do we see when events are emitted? Try this code:

/*globals require, console, process*/

var EvEm = require('events').EventEmitter;
var gcd = new EvEm();

gcd.on("hello", function () {
  console.log("Greetings!");
});

gcd.on("goodbye", function () {
  console.log("I must leave now.");
});

gcd.emit("hello");

console.log("Thanks for the greeting.");

process.nextTick(function () {
  gcd.emit("goodbye");
});

console.log("Can we say goodbye yet?");

And here is the output:

Greetings!
Thanks for the greeting.
Can we say goodbye yet?
I must leave now.

We start by loading the two events we will use. Then we emit “hello”. This has the immediate effect of writing “Greetings!” to the console. After that action is done, control returns to where the emit happened. Thus, the “Thanks for the greeting.” is written as it is the next line of code. We have now arrived at process.nextTick. This loads up a function into the queue that will, when it is its turn, emit “goodbye”. But we are still in the first tick of the event loop. Moving onto the next line, we get the question “Can we say goodbye yet?”. The first tick is done. The next tick is called and the “goodbye” event is emitted, leading to “I must leave now.”.

Emitting Later

The basic technique for queueing events should now be obvious: use process.nextTick. This is why there was no need to have emitting events be automatically put in the next tick. It is easy to delay emission. But if they were delayed later by default, it would be difficult to undo this.

But why do we want to emit now? First, it gives us an immediate flow. Events become more like calling functions of old. This may or may not be a good thing, but it is a common need. Even more fundamentally, node is a server-side technology. This means that we could be dealing with large number of requests. The queue could get quite large for incoming requests. If each of them were processed over-and-over, bit-by-bit, the memory overhead and time could get quite large.

But isn’t the whole point of node.js to do asynchronous logic? Yes and no. Think of it this way. Imagine a grocery store. We all have experiences where something goes wrong with the person in front of you and the line is held up. Imagine now that person being deftly put aside while other customers get serviced and the issue gets resolved by a manager. This is what node.js does. It takes the external bits, such as database calls, and makes it so that the server can continue to process other customers. When the database calls back, the customer gets back in line and is then dealt with. So while that particular customer has to wait a little extra, overall the experience is faster.

Imagine now if events were delayed at every opportunity. The analogue is that after each ringing up of an item, all the other customers get a chance for one item. And then it continues. This would cause greater delays, not less. And it would take the cash register many, many bits of data to store and correlate with.

So asynchronous for external calls and, if one wants, internal long running processes. But otherwise, the logic flows in sequence. And this is the efficient model.

If you have a regular need for emitting later, you can extend the prototype of EventEmitter to have an emitLater method:

EventEmitter.prototype.emitLater = function () {
  var self = this;
  var args = arguments;
  process.nextTick(function () {
    self.emit.apply(self, args);
  });
};

This is not optimized, but rather just the basic idea.

Here is the earlier example, modified:

/*globals require, console, process*/

var EvEm = require('events').EventEmitter;
EvEm.prototype.emitLater = function () {
  var self = this;
  var args = arguments;
  process.nextTick(function () {
    self.emit.apply(self, args);
  });
};


var gcd = new EvEm();

gcd.on("hello", function () {
  console.log("Greetings!");
});

gcd.on("goodbye", function () {
  console.log("I must leave now.");
});

gcd.emit("hello");

console.log("Thanks for the greeting.");

gcd.emitLater("goodbye");

console.log("Can we say goodbye yet?");

It has the same output as the first example.

You may want to use this technique if multiple actions respond to the same event and you want to ensure that all reactions to the first event are done with before processing any events called from those reactions.

Events calling themselves

Since events are emitted immediately, if an event leads to actions that call that very event, we enter recursive eventing. And just with normal function recursion, we can exhaust the call stack. Observe:

/*globals require, console, process*/

var EvEm = require('events').EventEmitter;

var gcd = new EvEm();

gcd.on("hello", function (count, times) {
  count += 1;
  if (count < times) {
    gcd.emit("hello", count, times);
  } else {
    gcd.emit("done", count)    ;
  }
});

gcd.on("done", function (count) {
  console.log(count);
});

gcd.emit("hello", 0, 1e3);

gcd.emit("hello", 0, 1e6);

The first count goes well, but the second exceeds the maximum call size. Again, this is because each function does not finish executing until after all subsequently called events and their actions have been resolved.

We can use the process.nextTick trick to avoid the call stack issue. But it places each event calling onto the queue. It will slow down the process.

/*globals require, console, process*/

var count = 0;
var times = 1e6;

var start = (new Date()).getTime();

while (count < times) {
  count += 1;
}

var diff = (new Date()).getTime() - start;

console.log("while diff: "+diff+" count: "+count);

var EvEm = require('events').EventEmitter;

var gcd = new EvEm();

gcd.on("hello", function (count, times, start) {
  count += 1;
  if (count < times) {
    process.nextTick(function () {
      gcd.emit("hello", count, times, start);      
    });
  } else {
    gcd.emit("done", count, start);
  }
});

gcd.on("done", function (count, start) {
  var diff = (new Date()).getTime() - start;
  console.log("event diff: "+diff+" count: "+count);  
});

gcd.emit("hello", 0, times,  (new Date()).getTime());

When I ran this, I saw that the while loop runs in 4 ms while the nextTicked/evented loop took 2000 ms. But it does avoid a call stack crash.

{ 2 } Comments

  1. Thomas Park | January 16, 2012 at 10:25 pm | Permalink

    Nice writeup and another nice analogy with the grocery store line.

  2. mythiclogos | January 17, 2012 at 8:09 pm | Permalink

    Thanks. I have been hiding from my daughter at Wegman’s cafe which overlooks their grocery store. I guess it was inspiring.

Post a Comment

Your email is never published nor shared. Required fields are marked *