207 Gruppen / 1747 Gestalten / 12825 Beiträge

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:

  1. something like emit and on methods to send messages and subscribe to them
  2. on should return something that allows me to cancel the subscription
  3. 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

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!