◆ EventTarget を使う
◆ 長いメソッド名や Event インスタンス作成を内部で行うサブクラスを作るとちょっと便利に

ブラウザで複数箇所に複数回通知をしたいときは Promise よりも Event の仕組みが向いています
EventEmitter がブラウザにもあるといいのですが ないのでよくシンプルなものを作ってます

const ee = new class {
fns = {}
on(type, fn) {
if (this.fns[type]) {
this.fns[type].add(fn)
} else {
this.fns[type] = new Set([fn])
}
}
off(type, fn) {
this.fns[type]?.delete(fn)
}
emit(type, ...args) {
for (const fn of this.fns[type] || []) fn(...args)
}
}

もっと高機能にもできますが 実際必要なものってリスナのつけ外しと呼び出しくらいで リスナ全削除やワイルドカードや once などはあっても使うシーンがかなり稀なので書く手間を考えるとこれで十分です

それでも毎回書くのってやっぱり面倒です
ブラウザには EventEmitter はなくても Event の仕組みはあります
window や document や各 HTMLElement など addEventListener を行えるものは EventTarget を継承しています
これが Event を扱えるものなので直接これのインスタンスを作れば EventEmitter に相当するものになります

const et = new EventTarget()
et.addEventListener("foo", eve => { console.log(eve) })
et.addEventListener("bar", eve => { console.log(eve) })

et.dispatchEvent(new Event("foo"))
// Event {isTrusted: false, type: "foo", ... }

et.dispatchEvent(new Event("bar"))
// Event {isTrusted: false, type: "bar", ... }

このままだと長いのでもう少し短く書けるようにします

class EE extends EventTarget {
on(...a) { this.addEventListener(...a) }
off(...a) { this.removeEventListener(...a) }
emit(type, ...args) { this.dispatchEvent(new CustomEvent(type, { detail: args })) }
}

const ee = new EE()
ee.on("foo", ({ type, detail }) => { console.log(type, detail) })
ee.on("bar", ({ type, detail }) => { console.log(type, detail) })

ee.emit("foo", 1, 2, 3)
// foo [1, 2, 3]

ee.emit("bar", 10, 20, 30)
// bar [10, 20, 30]

いい感じですね
リスナ関数が受け取るものは Node.js の EventEmitter と同じように emit で渡した引数そのままでもよかったのですが Event オブジェクトのほうがブラウザ的に慣れてますし type や timeStamp プロパティもあるしということでそのままにしました

Short で書いてた

今回の内容ですが 以前書いた気がするなぁと思いつつググっても

これとか これとか

ES5 で書かれた昔のものしか見当たらなかったので書いてみたのですが ほぼ同じのが Short のほうにあることにあとから気づきました
なぜか Short の方だとググっても出ないんですよね
なのでこっちにもほぼ同じですが書いておくことにしました