Ports called asynchronously if inside Cmd.batch
tl;dr
- Elm 0.19 allows catching events and sending them through ports in the same tick of the event loop.
- If we use
Cmd.batchthe port call does not happen in the same tick of the event loop.
Problem Statement
Certain DOM events need to be handled synchronously. Elm now allows us to do that through a port. However, if when dispatching the event to a port we use Cmd.batch and send other events with it, this port call is now handled asynchronously making the handling of certain events not possible again.
Here are the return values I tested for the update function and their outcome.
(model, sendToPort event)- gets called synchronously as expected(model, Cmd.batch [ sendToPort event] )- gets called synchronously as expected(model, Cmd.batch [ sendToPort event, otherCmd ])- gets called asynchronously, unlike expected.
If we use Cmd.batch with a list containing only the port command, the call is still synchronous. This means that the amount of elements in the batch list changes the program's behaviour.
SSCE
The full code is here https://ellie-app.com/3nJ8Pz5p7kba1, however, for some reason Ellie is not handling the synchronous port call correctly. I recorded the gif running the exact same code in Elm 0.19.0 locally.
In this example I'm using event.dataTransfer.setData, which must be called synchronously in the callback for the dragstart event.
- If called synchronously it adds the data specified to
event.dataTransfer.items. - If called asynchronously the invocation of
setDatais silently ignored.

Important elm bit
port handleEvent : Decode.Value -> Cmd msg
type Msg = Noop | WithoutBatch Decode.Value | WithBatch Decode.Value
update msg model =
case msg of
WithoutBatch event -> ( model, handleEvent event )
WithBatch event -> ( model, Cmd.batch [ handleEvent event, anotherCmd])
Noop -> ( Model, Cmd.none )
anotherCmd = Task.perform identity (Task.succeed Noop)
The JS part.
var app = Elm.Main.init({ node: document.querySelector('main') })
app.ports.handleEvent.subscribe(function addItemToEvent(event) {
console.log("Before adding item:", event.dataTransfer.items.length);
event.dataTransfer.setData("elm", "rocks");
console.log("After adding item:", event.dataTransfer.items.length);
});
I tracked it down to a more specific issue.
If I dispatch Task.perform identity (Task.succeed MyMsg) together with a port, the port call becomes asynchronous.
Here is a demo that works in Ellie showing that removing the extra command makes port call synchronous again. https://ellie-app.com/3nJ3z59nCdQa1
