Event

One problem of callbacks is that is a one to one pattern, it gets a little complicated when multiple participants want to get called. But that's still possible:

const listenForClick = (element, callback) => {
  const onclick = element.onclick
  element.onclick = ev => {
    onclick?.(ev) // or: onclick && onclick(ev)
    callback(ev)
  }
}

Everytime one needs to listen for the click event, call this function

listenForClick(document.body, ev => console.log(ev))

What a brilliant solution!

But there can be better ones, like event:

document.body.addEventListener('click', ev => console.log(ev))

Here the event is 'click', the listener is the function ev => console.log(ev).

You can add multiple listeners, there won't be any conflict like callbacks had.

And event better, you can remove them later.

const log = ev => console.log(ev)
document.body.addEventListener('click', log)
// to remove
document.body.removeEventListener('click', log)

implement event dispatcher using callbacks

type Listener<T> = (data?: T) => void

class EventEmitter<T> {
    private listeners: { [event: string]: Array<Listener<T>> } = {}

    on(event: string, listener: Listener<T>) {
        if (!this.listeners[event]) {
            this.listeners[event] = []
        }
        this.listeners[event].push(listener)
    }

    off(event: string, listener: Listener<T>) {
        if (this.listeners[event]) {
            this.listeners[event] = this.listeners[event].filter(x => x !== listener)
        }
    }

    emit(event: string, data?: T) {
        this.listeners[event]?.forEach(listener => listener(data))
    }
}