14
\$\begingroup\$

In an effort to rid my code of jQuery, I wrote a custom global event management/handling system. I was expecting this to be hard as, from what I've read, most people don't create their own. It's far simpler than most libraries I've seen. Is there a flaw in this method? What are the benefits of using something like EventEmitter.js?

var EvtMgr = function() {
  var events = {};
  this.on = function(evtName, callback) {
    if (!events[evtName]) events[evtName] = [];
    events[evtName].push(callback);
  }
  this.trigger = function(evtName, evtObj) {
    events[evtName].forEach(function(callback) {
      callback(evtObj);
    });
  }
  this.remove = function(evtName) {
    delete events[evtName];
  }
}

exports.evtMgr = new EvtMgr();

Then I can register listeners with evtMgr.on('foo', function(evtObj) {…});

\$\endgroup\$
0

3 Answers 3

8
\$\begingroup\$

I was expecting this to be hard as, from what I've read, most people don't create their own

Actually, there are more people who create event emitters than you think. I'm one of them, and here's my implementation. Aside from Event Emitter, it's also called "pub-sub", and here's one of the same nature under that label.

It's a basic practice of the concept of managing async and decoupled code. It also makes for a good practice in code performance, coding style and sample code when learning GIT.

It's far simpler than most libraries I've seen.

Because it is very simple. Collect callbacks, and when triggered, you look for the right collection and fire. That's about it.

Is there a flaw in this method?

Yes, and that's what I haven't fixed in mine. The following code in trigger is the problem:

events[evtName].forEach(function(callback) {
  callback(evtObj);
});

In your trigger, you fire the callbacks one after the other. That's fine for sequential code. But what if each of the callbacks contained operations that are synonymous to looping 100k times? And imagine if every callback listening to that fired event did that? That would freeze up the browser.

A solution to that is using timers. Timers can be "set aside" when JS is busy. They don't "block the thread", as they usually call it. That way, you have these "gaps" when executing the callbacks, allowing the browser to breathe and do something else, like render the page perhaps.

var length = events[evtName].length;
var i = 0;
var timer = setInterval(function(){       // Execute whenever possible
  events[evtName][i].call();              // Nothing special about the call()
  if(++i == length) clearInterval(timer); // Clear when done
},0);

What are the benefits of using something like EventEmitter.js?

If you mean the concept of pub-sub, then you have decoupled of code which means it's easier to put in and pull out code without breaking the rest of the app. You also have a basic facility for managing asynchronous events.

If you meant the library itself, then you just saved your time writing, testing and maintaining a pub-sub library. Not to mention, it's all free. All you need to do is grab and go (and maybe give credits where it's due).

\$\endgroup\$
0
3
\$\begingroup\$

Check out "A Less Convoluted Event Emitter Implementation" by Nicolas Bevacqua that explains the benefits of having the event triggering functionality as a mixin instead of just a plain constructor or centralized object.

Check out component/emitter and λ.emitter from bevacqua/contra for concrete examples.

The pattern would allow you to make anything an event emitter!

var dog = {};
EvtMgr(dog);
dog.trigger('wroof');
\$\endgroup\$
2
\$\begingroup\$

You are missing a key piece of functionality: Removing an event handler. You allow the removal of all event handlers, but not a single handler:

this.remove = function(evtName, callback) {
    if (!callback) {
        // clear all handlers for an event
        events[evtName] = [];
    }
    else if (events[evtName]) {
        // remove a single handler
        var i = 0, callbacks = events[evtName],
            length = callbacks.length;

        for (i; i < length; i++) {
            if (callbacks[i] === callback) {
                callbacks.splice(i, 1);
                break;
            }
        }
    }
};

If you ever need to write object oriented code, you might consider accepting a context argument to the on() method.

\$\endgroup\$
0

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.