Pub Sub / Event Driven architecture is a common practice in the world of Client & Server side JavaScript. I have been tasked to architect a very large Web Application using Dojo as Front End and node.js as backend. Pub / Sub seems very appealing as it allows for a great deal of parallelism amongst teams. I am afraid though, if there will be performance consequences.
I have a general question regarding the cost of Events and Event Handlers in JavaScript. I've already seen this, this, this, even this and this. But I still don't seem to see a general purpose answer. Independent of frameworks, suppose we have 2 methods
publish() //Like jQuery's / Dojo's trigger(), EventEmitter's emit()
and
subscribe() //Like jQuery's / Dojo's / EventEmiter's / DOM's on() connect() live() addEventListener()
object.publish('message1', data);
object.publish('message2', data);
...
object.publish('message100', data);
//All these are in separate files / modules
subscribe (object, 'message1', function (data) { A()...})
subscribe (object, 'message2', function (data) { B()...})
subscribe (object, 'message100', function (data) { Z()...})
data.message = 'message1'
object.publish('message', data)
subscribe (object, 'message', function (data) {
switch (data) {
case 'message1':
A();
break();
case 'message2':
B();
break();
...
case 'message100':
Z();
break();
}
})
object.publish('event', data);
//A.js
subscribe (object, 'event', function (data) {
A();
});
//B.js
subscribe (object, 'event', function (data) {
B();
});
//C.js
subscribe (object, 'event', function (data) {
C();
});
subscribe (object, 'event', function (data) {
A();
B();
C();
});
Q1: Can someone point me towards research and performance tests done for this in the Client Side (using DOMEvents or Custom Events), Server Side (EventEmitter and more in Node.js)? Its a simplistic example but can easily grow to 1000's of such calls as the app is pretty large. If not, how do I go about benchmarking myself for noticeable performance degradation? Maybe with something like jsperf? Any theoretical foundation to know hwy one is more performant than the other?
Q2: If Case 1s are more performant, what is the best way to write loosely coupled code? Any method of finding the middle ground? Writing the code like Case 1 but some intermediary compilation / build process to turn it to Case 2 (Something like what Google Closure compiler does in other perf cases?) say using [Esprima]. I hate to complicate the build process even more than it is. Is the performance boost (if any) worth all this?
Q3: Finally, although I'm looking for a very specific JavaScript specific answer here, it might help to know the performance costs in other languages / environments. The fact that in most cases events are hardware triggered (using the concept of interrupts) contribute anything to the answer?
Thanks to all who made it till the end of this Q!!! Much Appreciated!!!
Each architecture decision does have a performance impact as you have suggested:
Callback (fastest)
one to one binding has a direct reference to the function
315,873 ops/sec
Pub-Sub Events
too many binding requires a loop to call multiple callbacks, more callbacks = worse performance
291,609 ops/sec
Promises (slowest)
use a delay to ensure each item in the chain is not invoked at the same time, more chains = worse performance
253,301 ops/sec
I would choose callbacks for most cases unless you have a load of interconnected modules, then pub/sub is useful.
And remember these can work in tandem with each other. I usually allow a callback and a pub/sub event within the same module, letting the developer choose which one they would prefer to use:
var module = {
events: [],
item: { 'name': 'Test module' },
updateName: function(value, callback) {
this.item.name = value;
if (callback) {
callback(this.item);
}
this.dispatchEvent('update', this.item);
},
addEvent: function(name, callback) {
if (!this.events[name]) { this.events[name] = []; }
this.events[name].push(callback);
},
removeEvent: function(name, callback) {
if (this.events[name]) {
if (callback) {
for (var i=0; i<this.events[name].length; ++i) {
if (this.events[name][i] === callback) { this.events[name].splice(i, 1); return true; }
}
}
else { delete this.events[name]; }
}
},
dispatchEvent: function(name, data) {
if (this.events[name]) {
for (var i=0; i<this.events[name].length; ++i) {
this.events[name][i]({ data: data, target: this, type: name});
}
}
}
};
module.updateName('Dan'); // no callback, no event
module.updateName('Ted', function (item) { console.log(item); }); // callback, no event
module.addEvent('update', function (item) { console.log(item); }); // add an event
module.updateName('Sal'); // no callback, event callback
module.addEvent('update', function (item) { console.log('event2', item); }); // add a second event
module.updateName('Jim'); // no callback, two event callbacks
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With