A WebSocket connection gives an application a way to exchange messages. It does not tell the interface which operation a reply belongs to, how long to wait, or whether a repeated request should execute again. Those are application decisions.
The request/response playground makes those decisions visible. It runs entirely in the browser: there is no live socket, no backend, and no measurement of real network latency. The goal is to inspect a small protocol under conditions that are easy to reproduce.
The problem: a reply is not yet an outcome
Imagine a page with two pending operations. The second finishes first. Matching the next reply to the oldest request updates the wrong operation. Add a deadline, and another question appears: what should happen when a successful reply arrives after the interface has already shown a timeout?
A second click or an application retry can also submit the same logical request again. Suppressing an extra reply in the browser protects the interface, but does not undo a repeated server-side effect. The demo separates those two responsibilities.
Three decisions in the implementation
Correlate by ID. Each new operation receives a request ID. Extra copies retain it. One row represents one logical operation, regardless of the number of deliveries. The sequential IDs are readable teaching aids; a real protocol needs IDs scoped to its sessions and retry policy.
Settle once. An operation starts as pending and ends as successful, failed, or timed out. The response handler checks the state before changing it. A response arriving after settlement is recorded and ignored. A successful or failed response cancels the deadline timer. At equal configured times, the deadline wins because the simulator registers it first.
The core guard is small:
if (operation.status !== 'pending') { log(operation.id, 'ignored'); return;}Deduplicate execution separately. The simulated server chooses one result per request ID. Copies replay that result, with their replies staggered by 80 milliseconds to make them visible. The client still accepts only the first timely reply. This models the effect of server-side deduplication; it does not implement a durable idempotency store.
The state model is separate from the page controls. An injected clock lets checks advance time deliberately, and an injected random source makes failure cases deterministic in tests. In the UI, a failure rate between zero and one hundred is sampled independently for each logical operation. The zero and one hundred presets are repeatable.
Try the boundaries
Start with Happy path and send a request. Before it finishes, lower the latency and send another: the later request can finish first while retaining its own ID.
Next, select Late reply. With a 2,500 ms response delay and a 1,000 ms timeout, the row times out before the reply appears in the timeline. It stays timed out afterward. A timeout ends the client wait; it says nothing conclusive about whether a real server executed the request.
Finally, use Duplicate requests. Two extra copies produce three deliveries, one server result, and one client outcome. Switching settings affects only newly sent operations, so mixed conditions remain understandable in the same run.
What the simulation leaves out
A local simulation trades realism for control. It cannot validate network behavior, connection loss, reconnects, authentication, or backpressure. “Failed” means an explicit application error response; it does not mean a dropped packet. Browser timers also are not precision clocks, especially in background tabs.
A production design would need message validation, connection lifecycle handling, and a bounded pending-request registry. Retrying a write requires an explicit idempotency policy: key scope, result retention, concurrent duplicates, and atomicity between storing the key and applying the effect all matter. A new request ID on every retry would defeat deduplication by ID.
The playground bounds a run to 40 operations and retains the latest 100 events. Clearing the run or leaving the page cancels its timers. These limits keep an educational session inspectable; they are not a throughput benchmark.
Lessons
The useful distinction is between delivery, execution, and the result shown to a person. A request may be delivered repeatedly, executed once, and still time out at the client. Seeing those events in separate rows and timeline entries makes the distinction easier to reason about.
Start with a small set of invariants: identify the operation, define when waiting ends, and decide which layer owns duplicate handling. Then test the awkward boundaries before adding reconnects and retries.
Open the Lab and try a different set of conditions. For the actual browser API, MDN’s WebSocket client guide covers sending, receiving, and connection cleanup.
Comments