PubSub / Event Emitter Implementations
While working on the current stadtgestalten release I investigated a lot of pub-sub / event emitter libraries in order to have some kind of application event bus. As we’re trying to keep JavaScript unobstrusive (JavaScript should be optional but is used it in a lot of places to improve user experience) our frontend application core is based on a lot of hooks found in the markup. Take the text editor as an example. We’re using a simple textarea
element in the backend as part of an oldschool form. The textarea
has a data-component
attribute with editor
as its value. Once the JavaScript is loaded this happens:
import { component } from 'luett'
import editor from './transforms/editor'
component('editor', editor)
The code above is basically everything we do to initialize components on the page. component
starts looking for elements with data-component="editor"
attributes and calls editor for each of the elements (there’s a bit more to it, but that’s the idea). This works surprisingly good given the fact how complicated component management can be. But sometimes components need to talk to each other . And you know what would be great for that? Yes! An application bus! Component A writes a message to the bus and component B, a strong believer in everything A has to say, subscribes to its messages. Easy as pie.
import { component } from 'luett'
import editor from './transforms/editor'
import cite from './transforms/cite'
import imaginaryBus from 'imaginaryBus'
const opts = { bus: imaginaryBus }
component('editor', editor, opts)
component('cite', cite, opts)
Still looks very readable right? What was driving me nuts is the current state of pub sub subscription interfaces. I took a little tour on npm to find implementations that fulfill some specific but reasonable (at least as far as I am concerned) requirements:
- something like
emit
andon
methods to send messages and subscribe to them on
should return something that allows me to cancel the subscription- the implementation shouldn’t be a singleton (modules by default are singleton-ish because they’re only loaded once, but you may still call a factory function they return). In my language:
import ImaginaryBus from 'imaginaryBus'
const myBus = ImaginaryBus()
const token = myBus.on('foo', data => { console.log(data) })
myBus.emit('foo', 'Hello World')
// hello world is logged
token.destroy()
See where I’m going? But basically every module either breaks requirement 2. or 3. Most of the time the modules provide an off
implementation. I am supposed to call it with the same arguments that I called on
with. Which is weird because why would I want to do that. See that little data => { console.log(data) }
function in my code? That’s an anonymous function and in order to call off
with the same arguments as on
I’d first have to create a reference to it. But you know who already has a reference to it? on
has! So why doesn’t it return a function that I can call to cancel the event listener / subscription?
In addition to that a lot of libs broke requirement 3. which is kind of lame. I know libraries on npm are somewhat destined to be small, but having a callable interface that works as a factory won’t cost you anything.
In the end I just wrote a few lines of code to wrap eventemitter3 that work like a charm. But I’m still kind of confused why no one bothers to implement non-singleton token-based subscriptions. Am I the one who who’s being weird? Tell me!