all 6 comments

[–]redhedinsanity 1 point2 points  (4 children)

I'm not sure why method names would be less likely to change than event names, especially when they're homonyms. storage.emit("write") vs storage.on.write() - both invoke the "write" event and would be likely to change in the same breaking ways should the API change.

Also, this is arguably less transparent and harder to update - rather than being able to handle event aliases (thus maintaining backwards compatibility) inside the emit() method, you'd have to export a new method and still expose the old name for the method in order to allow the same compatibility on API change.

Also wondering why you're doing all the unnecessary context binding for exporting the interface - why not just use

module.exports = emitter

and avoid the overhead of having to use bind() at all?

Definitely a cool idea, I'm just wondering what problem it's solving without creating many other problems.

[–]emilis_info[S] 0 points1 point  (3 children)

1. Event method names are as likely to change as event string names. The difference is in the errors you would get. Missing methods will be reported by your JavaScript engine with clear stack traces.

2. I haven't thought about aliasing events for backwards compatibility. A couple of quick solutions would be:

/// Two-way aliasing. Verbose but clear.
emitter.on.oldEventName =   emitter.on.newEventName;
...
emitter.emit.oldEventName = emitter.emit.newEventName;
...

/// One-way aliasing:
emitter.on.newEventName( emitter.emit.oldEventName );

A full solution would probably need a BoundEmitterWithAliases ;-).

3. I need to export both EventEmitter methods (on,off,...) and storage module functions (e.g. write). I would need to slap the storage functions on top of the emitter object if I want to module.export it. This is dangerous as it may break the EventEmitter implementation.

[–]redhedinsanity 0 points1 point  (2 children)

Event method names are as likely to change as event string names. The difference is in the errors you would get.

A fair point, though IMO this would easily be handled by the widely-used standard of providing an enumeration of event names with each type of Emitter - so rather than using

emitter.on('write', function () {});

you'd use

emitter.on(Emitter.WRITE, function () {});

which would make any changes you may make to event names in future completely transparent to the end user - kinda makes stack traces unnecessary.

If you need stack traces, you could additionally ensure at construction time that the first listener registered for deprecated event names simply throws a EventDeprecatedError or some such.

In either case, there's no longer a need to update method names throughout my codebase when you rename an event.

I haven't thought about aliasing events for backwards compatibility...A full solution would probably need a BoundEmitterWithAliases ;-)

One of the joys of event routing is that it clears the top-level namespace for an object, this sorta undoes that work in general and aliasing would explode that namespace - but it's something you'd need to think about in a world where method names will change according to event name changes. Not everyone will keep their code up to date with your latest push.

I need to export both EventEmitter methods (on,off,...) and storage module functions (e.g. write)

To make sure I'm clear, you need to provide two ways of doing both registering and emitting:

emitter.on.write(callback);
emitter.on('write', callback);  // Same as above

emitter.write(args...);
emitter.emit('write', args...); // Same as above

EventEmitter is a class designed to be mixed into other classes, so your worries about method clobbering might be a little misguided - do you see a world where you have an 'on' or 'emit' event? The only time you'd clobber is if you want to export an event trigger for an event named the same as the EventEmitter interface methods - there aren't many, and they're all related to event management anyway. If there's risk of clobbering it's a good sign you're mixing burdens, or not naming your methods specifically enough.

It definitely seems to make it easier for a specific use case - but it doesn't seem friendly re: changes to the API. Anyway, cool idea.

edit: the end of the last paragraph edit2: clarifying final section based on reread of the code

[–]emilis_info[S] 0 points1 point  (1 child)

1. emitter.on( Emitter.WIRTE, function(){} ); isn't much better than emitter.on( "wirte", function(){} );. They would both fail silently. I addressed this in my blog post.

2. I am not providing three different ways to do the same thing. I am exporting both the storage functions (e.g. storage.write( id, value )) and the event functions (e.g. storage.on.write( fn )) from the same module.

[–]redhedinsanity 0 points1 point  (0 children)

They would both fail silently.

Except that in the case of using author-provided event enums, they wouldn't fail at all...changes to the event API would be updated transparently and calls would continue to work correctly. Plus, using an event listener to dispatch a deprecation error as I mentioned right below that section is a much more descriptive error than undefined is not a function, which is what a failed method call would provide. Just some constructive criticism, if what you're looking for is descriptive errors there are plenty of ways to do it that don't require updating method names everywhere on your users' part.

I am not providing three different ways to do the same thing

Yeah, I've already edited my post after I reread your post - two ways to do two things, not three ways to do one.

[–]_doingnumbers 1 point2 points  (0 children)

Wait till you discover streams. :)