Eye Training · Events, Listeners and Sockets
Every on asks four questions: when is it removed, how many times is it added, what if it fires twice at once, and what happens across a reconnect?
Part 2 · Surface 14. Bug counts are hidden and some snippets are clean. See Start Here for how to use these drills.
The snippets use the browser WebSocket, Node's EventEmitter, and Socket.IO (socket.on, socket.emit, io.to(room)). The habits are the same for all three.
J · The basics
Bug hunt
J · Open a chat room
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 1 min
Bug hunt
J · Watch prices
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 1 min
Bug hunt
J · Keyboard controls
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 1 min
Bug hunt
J · Subscribe to live orders
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 1 min
Bug hunt
J · Shortcuts with cleanup
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 1 min
M · Three years in, these should jump out
Bug hunt
M · Render the cart
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 1 min
Bug hunt
M · Uploader
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 2 min
Bug hunt
M · Reconnect
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 1 min
Bug hunt
M · Socket.IO subscriptions
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 1 min
Bug hunt
M · Who's online
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 2 min
Bug hunt
M · Live stats
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 1 min
Bug hunt
M · Wait for ready
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 2 min
S · Where seniors stand out
Bug hunt
S · Balance updates
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 2 min
Bug hunt
S · Shared cursors
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 1 min
Bug hunt
S · Resume after reconnect
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 2 min
Bug hunt
S · Resume, done right
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 1 min
Bug hunt
S · Team rooms (server)
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 2 min
What your eye checks
Whenever you see on, addEventListener, onmessage or once, ask:
- Where is it removed? Find the matching
off/removeEventListener/abort(). If there isn't one, what ends its life? - How many times does this line run? Inside a render, a
connecthandler, a function called per room or per request, it adds a listener every time. - Is
offgiven the same function object? Inline arrows and.bind()create new ones. - Can it fire before the listener exists, or before the socket is open?
- Who handles
'error'? In Node, an emitted'error'with no listener throws. - What if two events are handled at once? Async listeners run concurrently; read-modify-write inside them races.
- What happens across a reconnect? Re-subscribe (in
connect), don't re-register listeners, resume from the last seen id, and back off with jitter. - Can events arrive twice or out of order? Make handlers idempotent and compare versions or sequence numbers.
- Server side: who checked that this client may join or send to this room? Room ids from the client are untrusted input.
- What does the connection own? Timers, subscriptions and map entries started per connection must be cleaned up on
disconnect.
Tells
| You see | Ask |
|---|---|
.on( inside a function that runs more than once | How many listeners after five calls? |
.on('connect', () => { … .on( | What happens on every reconnect? |
off(… (x) => or .bind(this) in removeEventListener | Is it the same function? |
new WebSocket(…) followed by .send | Is it open yet? |
this.emit('error' | Is anyone listening? |
onclose = () => connect() | Backoff? Jitter? A limit? |
map.set(userId, socket) | What about a second tab? |
setInterval inside on('connection' | Who clears it? |
.on('x', async with read-then-write inside | What if two arrive at once? |
A version or seq field that's never read | What if they arrive out of order? |
socket.join(roomId) with a client-sent id | Who checked access? |
Go deeper
- Cancellation and Cleanup: Part 6, cleaning up what you started
- Race Conditions: stale responses and out-of-order events
- Casebook V: React and React Native: the same listener bugs inside effects
- Previous surface: Timers · Next: Streams and streaming data