JavaScript Callbacks vs Events – Advantages in Node.js

javascriptnode.js

I am a novice JavaScripter and have no real knowledge of what goes on inside the V8 engine.

Having said that, I am really enjoying my early forays into the node.js environment but I find that I am constantly using events.EventEmitter() as a means to emit global events so that I can structure my programs to fit a notifier-observer pattern similar to what I would write in say an Objective-C or Python program.

I find myself always doing things like this:

var events = require('events');

var eventCenter = new events.EventEmitter();

eventCenter.on('init', function() {
    var greeting = 'Hello World!';
    console.log('We're in the init function!);
    eventCenter.emit('secondFunction', greeting);
});

eventCenter.on('secondFunction', function(greeting) {
        console.log('We're in the second function!);
        console.log(greeting);
    eventCenter.emit('nextFunction');
});

eventCenter.on('nextFunction', function {
    /* do stuff */
});

eventCenter.emit('init');

So in effect I'm just structuring 'async' node.js code into code that does things in the order I expect, instead I'm kind of "coding backwards" if that makes sense. Would there be any difference in doing this in a callback-heavy manner, either performance-wise or philosophy-wise? Is it better to do the same thing using callbacks instead of events?

Best Answer

The nice thing about callbacks is there's no global state there, and passing parameters to them is trivial. If you have a function download(URL, callback: (FileData)->void) then you can know that's a self-contained higher-order function which essentially lets you construct a "grab this and do that" function. You can be sure your code flow is exactly as you expect, because nobody else even has a handle on that callback, and that callback doesn't know about anything but the parent function's given parameters. That makes it modular and easy to test.

If you now want to download 5 files in parallel and do different things, you need only fire five of these functions off with the appropriate callback functions. In a language with good anonymous function syntax this can be incredibly powerful.

Events, on the other hand, are more designed for notifying 1..* users of some state change. If you fire a "download complete" event at the end of download(URL), which launches processDownload() which knows where to find the data, you're tying your implementations of things to a larger amount of state. How do you parallelise downloads now? How do you handle the different downloads differently? Is download(URL, eventId) elegant? Is downloadAndDoThing(URL) elegant? Hardly.

Of course, as with all things, you are making tradeoffs. Nested callbacks can make the order of execution of code more confusing, and the lack of easy global accessability makes it a poor choice for anything where you do have a 1..* relationship between producer and consumer. You have a harder time passing data around, but if you can get away with not having additional state then that's usually a benefit anyway.

Regardless. You're coding in node.js where callbacks are idiomatic, and the language and libraries are designed around their use. Whether you see advantages in one design or another, I think it's almost always true that writing idiomatic code in any language is going to have far greater support and make your life much easier than trying to circumvent it.

Related Topic