Elm ports and Task scheduling under concurrent messages: controlling UI update latency
0 reputation · 03 Oct 2025, 18:59 UTC
0 reputation · 03 Oct 2025, 18:59 UTC
An Elm 0.19 application receives messages from several ports and HTTP tasks at roughly the same time. The goal is to keep UI updates responsive when these asynchronous sources emit in bursts, rather than having renders queue up behind a backlog of queued messages.
Elm's runtime is single-threaded and schedules updates on the JavaScript event loop, so messages from concurrent requests are processed sequentially. The ordering of port messages arriving from multiple sources appears unspecified, and Task exposes no built-in prioritization or throttling. It is unclear whether observed latency comes from message queueing in the Elm runtime, from batching behavior in the 0.19 scheduler, or simply from the browser's event loop under load.
update does not run once per message during bursts?31275 reputation · 03 Oct 2025, 19:48 UTC
Elm 0.19 does not guarantee ordering or batching of messages that arrive from different ports in the same event‑loop turn; each port message creates its own external message that is queued and processed one after another. To keep the UI responsive you can coalesce rapid port traffic by keeping only the latest value and acting on it after a short delay (or on animation frames). To diagnose whether frame drops come from the Elm message queue or from browser rendering, record a Chrome DevTools performance profile and compare the length of JavaScript tasks (labeled “EventListener”/“Script”) with paint/compositing intervals.
The observed latency during bursts is most likely caused by the Elm runtime invoking update once for each queued port message, because the runtime processes external messages sequentially on the JavaScript event loop. If each update does non‑trivial work, the UI thread blocks and frames are dropped.
Task values are turned into commands that are also queued as external messages; they have no priority mechanism.Time.delay or requestAnimationFrame via a port) guarantees at most one update per interval, discarding intermediate values.update; long Paint/Render/Gpu intervals indicate browser‑side rendering work.latest : Maybe ( Payload , Time ).update handler for each port message, replace the stored payload with the new one and update the timestamp.Task.perform that waits a short interval (e.g. 16 ms for ~60 fps) using Time.delay or a port that requests requestAnimationFrame.update to verify that bursts now produce fewer update calls.Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.