Challenge 3: Explain a Duplicate-Handler Bug — Possible Solution ==================================================================== THE BUGGY PATTERN ------------------- class BrokenReconnectingSocket { constructor(url) { this.url = url; this.socket = new WebSocket(url); this._attachHandlers(); // attaches handlers to this.socket ONCE, here } _attachHandlers() { this.socket.addEventListener("message", (event) => { renderMessage(JSON.parse(event.data)); }); this.socket.addEventListener("close", () => { // BUG: creates a NEW WebSocket, but never calls _attachHandlers() // again on it — or worse, calls a connect() that DOES re-attach, // but the developer also left old references/handlers dangling // elsewhere in the app that still point at the ORIGINAL socket. this.socket = new WebSocket(this.url); }); } } WHAT ACTUALLY GOES WRONG -------------------------- There are two closely related failure modes this pattern can produce, depending on exactly how the mistake is made: 1. If _attachHandlers() (or equivalent event-listener setup) is NOT called again after creating the new WebSocket in the "close" handler: the new socket has NO "message" handler at all — incoming messages on the reconnected socket are simply never processed, and the application silently stops receiving anything after the first disconnect/reconnect cycle, even though the connection itself is technically alive again. 2. If handler setup IS repeated on each reconnect, but something ELSE in the application still holds a reference to the OLD socket object (for example, a variable captured in a closure before the first disconnect, or a second piece of code that separately called oldSocketReference.addEventListener(...) directly) — those old listeners are still attached to the OLD, now-permanently-closed WebSocket object. They won't fire again (a closed socket doesn't receive messages), but they're also never cleaned up, silently accumulating as dead, inert listener registrations with every reconnect — a genuine memory leak that grows with each reconnection over a long-lived page session. THE FIX -------- - Ensure that EVERY time a new WebSocket instance is created (on initial connect AND on every reconnect), the full set of event listeners is re-attached to THAT specific new instance — treat "create a socket" and "wire up its listeners" as one atomic step that always happens together, exactly as the connect() method in this chapter's ReconnectingSocket class does. - Never let other parts of the application hold a long-lived direct reference to one particular WebSocket object. Instead, they should go through the wrapper (e.g. reconnectingSocket.send(...), or reading reconnectingSocket.socket fresh each time) so they always interact with whichever socket instance is CURRENTLY live, rather than one that may have already been replaced behind the scenes. - If old listeners genuinely must be removed explicitly (rather than just left to be garbage-collected along with the closed socket object), use removeEventListener with the same function reference before discarding a socket — though in practice, keeping all listener setup centralized in one place (as above) usually avoids ever needing this step at all.