When is `Cmd` actually processed?

When is Cmd specified in update function actually processed?
Is it the same timing when DOM changes begins, or after completing DOM changes?

For instance, in the example bellow, is the someCmd actually processed after successfully changed DOM structure for changeToModel?

update msg model =
    case msg of
        SomeMsg ->
            ( changeToModel model
            , someCmd
            )

If it is not after completed DOM changes, how can we write actions that should be processed after DOM changes (e.g., scroll events for new DOM structures)?

5 Likes

The exact sequence is something like this:

  1. update returns model and a batch of commands
  2. an animation-frame is requested with the diffing and rendering of the view as its callback
  3. commands and subscriptions are “distributed” to their effect managers

This ordering has an interesting property: if a command (like an HTTP request) doesn’t care about the view being rendered first, it won’t “wait” for that to happen. However, if a command (like Dom.focus) does care, it can wait for the view to be rendered rather simply: all it needs to do is wrap itself in requestAnimationFrame.

The trick is that calling requestAnimationFrame multiple times with different callbacks will ensure that the callback are also executed in the order in which they were queued. Since the view is queued before commands begin executing, commands can use this trick to ensure they run after view-rendering.

Libraries like elm-lang/dom already use this trick, so when you return ( { model | renderMyDiv = True }, Task.attempt FocusResult (Dom.focus "input-in-my-div") ) for example, the view is rendered before focussing is actually attempted.

When you’re dealing with ports, you can use a similar trick. On the Elm side, nothing changes - you simply call your port. On the JS side, you can do something like this:

elm.app.ports.foo.subscribe(function (id) {
  requestAnimationFrame(function () {
    /* when this callback executes, the view should have rendered. */
  })
})
26 Likes

Thanks a lot!
The way for ports is also super helpful for me.

I feel like this answer should be put into the docs somewhere. Super helpful.

6 Likes

Great explanation! Thank you! I believe that Evan (or maybe someone else) mentioned that this is going to change (maybe optionally?) in 0.19 allowing you to run Cmds (or perhaps this was for ports) in the same tick as the code that called it. This was in the context of supporting certain events that require code to be run sync (as opposed to async via setTimeout or requestAnimationFrame). I’ll see if I can find the link when I’m not on my phone, but curious if I’m understanding that correctly…

UPDATE: I did find the post in question, and Evan actually said that ports are changing to be able to be run as direct event handlers, not Cmds.

1 Like