This was a simple pub sub system I wrote based on some code I saw in the past.
This came from a time where jQuery was the popular library to solve website building problems for frontend.
This was written before ES6 so we didn’t have let and const yet. I wrote it to be a Revealing Module Pattern In JavaScript.

var emitter = (function () {
  "use strict"
 
  //VARS
  var _name = "Emitter"
  var ledger = {} // Stores the subscribed callbacks to be triggered on event emit.
 
  //GETTERS
 
  // Return a view of the ledger / mostly for fun
  function viewLedger() {
    var l = ledger
    return l
  }
 
  // FUNCTIONS
 
  // subscribe to an event
  function subscribe(eventName, fn) {
    console.debug("%s [ subscribe ] : ( %s )", _name, eventName)
 
    // Creates an empty array on the ledger object if one doesn't exist already
    ledger[eventName] = ledger[eventName] || []
 
    // Add the function to be called when this event is emitted
    ledger[eventName].push(fn)
  }
 
  // unsubscribe an event
  function unsubscribe(eventName, fn) {
    console.debug("%s [ unsubscribe ] : ( %s )", _name, eventName)
 
    if (ledger[eventName]) {
      for (var i = 0; i < ledger[eventName].length; i++) {
        if (ledger[eventName][i] === fn) {
          ledger[eventName].splice(i, 1)
          break
        }
      }
    }
  }
 
  // Emit event(s)
  function emit(eventName, data) {
    console.debug("%s [ emit ] : ( %s )", _name, eventName)
 
    if (ledger[eventName]) {
      console.debug("\tFound( %s ) %s to emit.", ledger[eventName].length, eventName)
      ledger[eventName].forEach(function (fn) {
        fn(data) // call the fn of each "subscribed" event
        console.debug("\t\t callback ran:", fn.toString().split(" ")[1])
      })
    } else {
      console.debug("\t No Subscribers")
    }
  }
 
  //EXPOSE
  return {
    ViewLedger: viewLedger,
    Subscribe: subscribe,
    Unsubscribe: unsubscribe,
    Emit: emit,
  }
})()

Modern version?

Testing out a modernized version of the script

class Emitter {
  #name = "Emitter"
  #ledger = new Map()
 
  viewLedger() {
    return Object.fromEntries(this.#ledger)
  }
 
  subscribe(eventName, fn) {
    console.debug(`${this.#name} [ subscribe ] : ( ${eventName} )`)
    if (!this.#ledger.has(eventName)) this.#ledger.set(eventName, [])
    this.#ledger.get(eventName).push(fn)
  }
 
  unsubscribe(eventName, fn) {
    console.debug(`${this.#name} [ unsubscribe ] : ( ${eventName} )`)
    const events = this.#ledger.get(eventName)
    if (!events) return
    this.#ledger.set(
      eventName,
      events.filter((cb) => cb !== fn),
    )
  }
 
  emit(eventName, data) {
    console.debug(`${this.#name} [ emit ] : ( ${eventName} )`)
    const events = this.#ledger.get(eventName)
    if (!events?.length) {
      console.debug("\tNo Subscribers")
      return
    }
    console.debug(`\tFound( ${events.length} ) ${eventName} to emit.`)
    events.forEach((fn) => {
      fn(data)
      console.debug(`\t\tcallback ran: ${fn.name || "<anonymous>"}`)
    })
  }
}
 
// usage
const emitter = new Emitter()
emitter.subscribe("test", (msg) => console.log("received:", msg))
emitter.emit("test", "hello world")