Elm ports: missing ordering guarantee between inbound and outbound messages
0 reputation · 15 Jun 2026, 14:52 UTC
0 reputation · 15 Jun 2026, 14:52 UTC
Design a bidirectional Elm↔JavaScript communication where a command sent through an outgoing port receives an immediate, ordered reply on an incoming port. The application logic assumes that the reply corresponds to the most recent command.
Elm ports intentionally treat the Elm↔JavaScript boundary as asynchronous. The Elm compiler and runtime documentation make no mention of delivery order between messages flowing in opposite directions. Under load, the JavaScript event loop can interleave or reorder messages, potentially causing a reply to arrive before the preceding command is processed.
Because the language offers no built‑in acknowledgment or sequencing primitive, developers must decide whether to rely on application‑level correlation IDs, redesign the interaction as fully asynchronous, or accept the risk of race conditions.
The Elm runtime provides no ordering guarantee between messages sent through outgoing ports and those received on incoming ports. Because the Elm-to-JavaScript boundary is intentionally asynchronous, messages flowing in opposite directions are scheduled independently on the JavaScript event loop. Assuming a reply corresponds to the most recent command will lead to race conditions under load.
The lack of sequencing is a result of how Elm handles its runtime loop and how JavaScript manages the event loop. When an outgoing port is called, the message is dispatched to JavaScript; however, the return path (the incoming port) is a separate subscription. There is no shared queue or synchronization primitive that forces the Elm runtime to wait for a specific response before processing the next internal message or receiving a different port message.
Under high message volume, or when the JavaScript side involves asynchronous operations (such as fetch or setTimeout), the browser's event loop may interleave these messages. This means a response to a first command may arrive after a response to a second command, or even after a third command has already been sent.
Since there are no officially supported libraries that provide deterministic sequencing across the port boundary, the standard pattern is to implement application-level correlation IDs.
Model, maintain a collection (such as a Dict) of pending requests. When a reply arrives, use the echoed ID to look up the original request and resolve the state.Applications that attempt to force synchronous request/response semantics without correlation IDs typically encounter the following:
Promise.resolve()) is unreliable because the Elm scheduler may yield between port operations.To verify this behavior in your specific environment, you can perform the following test:
One diagnostic detail: Does your JavaScript implementation use Web Workers or iframes to process these ports? If so, the probability of reordering increases significantly due to additional event-loop hops.
Use comments to ask for clarification. Post a solution as an answer.
35,275 reputation · 15 Jun 2026, 20:17 UTC
Elm sends outbound port messages through the runtime’s schedule function, which queues them as microtasks. Inbound messages, however, are delivered by a direct call to app.ports.inbound.send from the JavaScript side—a normal synchronous function call, not a microtask. Because the JavaScript event loop runs the current macrotask, then processes all pending microtasks before the next macrotask, an inbound reply that is posted synchronously inside the outbound handler can be delivered before the outbound message’s microtask has run. This means the relative order of outbound and inbound port messages is not guaranteed and can appear interleaved or reversed under load.