Last active
March 11, 2022 15:25
-
-
Save domenic/9003334 to your computer and use it in GitHub Desktop.
Revealing constructor pattern event-emitter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This event emitter emits events, but reserves the right to publish events to | |
// for its creator. It uses a WeakMap for true encapsulation. | |
const eesToEventMaps = new WeakMap(); | |
export default class EventEmitter { | |
constructor(publisher) { | |
const eventMap = Object.create(null); | |
eesToEventMaps.set(this, eventMap); | |
publisher(makePublish(this)); | |
} | |
on(eventName, handler) { | |
const eventMap = eesToEventMaps.get(this); | |
let handlers = eventMap[eventName]; | |
if (!handlers) { | |
handlers = eventMap[eventName] = []; | |
} | |
handlers.push(handler); | |
} | |
off(eventName, handler) { | |
const eventMap = eesToEventMaps.get(this); | |
const handlers = eventMap[eventName]; | |
if (!handlers) { | |
return; | |
} | |
const index = handlers.indexOf(handler); | |
if (index === -1) { | |
return; | |
} | |
handlers.splice(index, -1); | |
} | |
} | |
function makePublish(ee) { | |
const eventMap = eesToEventMaps.get(ee); | |
return function (eventName, ...args) { | |
const handlers = eventMap[eventName]; | |
if (handlers) { | |
handlers.forEach(h => h(...args)); | |
} | |
}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const myEE = new EventEmitter(publish => { | |
// Wait for interesting things to happen, then call | |
// `publish("eventName", ...args)`. | |
}); | |
passToOtherCode(myEE); | |
// The other code only gets access to `on` and `off`: it cannot trigger spurious | |
// events, but only listen for them. This makes it safe to pass `myEE` to | |
// multiple consumers without worrying about them accidentally stepping on each | |
// other's toes. | |
// (Of course, they could intentionally step on each other's toes, e.g. by | |
// overwriting `on` or `off`. For true security you'll need to deeply-freeze | |
// `myEE`, and do a few more things, e.g. to protect against plan interference | |
// attacks. We're not focused on that right now, but instead on the API | |
// ergonomics.) |
@domenic, please let me know if I'm close: this pattern of the constructor accepting a publish makes this EventEmitter class not ideal for extending, and more ideal for other code to contain references to an EventEmitter.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This pattern is awesome!