Batch messages one after the other

The docs say that there is no particular order when batching messages, which is a reasonable default. I do, however, require a specific order of batched messages. Is there some workaround or other strategy in the Elm architecture of achieving this?

1 Like

Are they all Tasks?

Sorry, I don’t actually know what a Task is. I am only considering switching to Elm from Purescript. They are effectful functions that need to execute on the Javascript side (through ports). Optimally this would be synchronous, but I believe ports don’t allow that.

One solution I thought of, is to have a special Msg that takes a message and an array of further messages and then “loops” through until all messages are handeled

Your last sentence is pretty much what I would have suggested. Is there a reason why all of these messages are separate instead of just one message? A little more context would be helpful.

That would work.

===

For Cmd.batch, the docs say:

“When you need the runtime system to perform a couple commands, you can batch them together. Each is handed to the runtime at the same time, and since each can perform arbitrary operations in the world, there are no ordering guarantees about the results.”

I suspect they get pushed onto a List, and then executed by repeatedly taking the head of the list until none are left. So the order would be backwards. That is based on what I have observed, but I did not really ever dig into it.

However, as that is an implementation detail, the docs are right to say no ordering is guaranteed.

Is it? I tried it with a purescript library and there they just all get dispatched at the same time asynchronously, so they end up out of order

I just have two separate messages that manipulate the DOM outside the scope of Elm and then I have a button that performs two such manipulations

Is this what you are looking for? It’s old code from dullbananas/editsc

type Msg
    = BatchMsg ( List Msg )

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        BatchMsg [] ->
            ( model, Cmd.none )

        BatchMsg (x :: xs) ->
            let
                ( newModel, cmd ) = update x model
            in
                ( newModel, Cmd.batch
                    [ cmd
                    , Task.succeed () |> Task.perform ( always (BatchMsg xs) )
                    ] )

actually that wont work

Why wouldn’t it work?

Here is a working implementation of your approach.

it uses cmd.batch which has no guaranteed order

Oh… right. However, unwinding the list like this would most likely result in the commands being sent in order.

I mean, on my machine, it does not matter if it is Cmd.batch [ cmd, sendMsg (Batch xs) ] or Cmd.batch [ sendMsg (Batch xs), cmd ]

I believe the implementation happens to perform all commands from a given update before commands from subsequent updates, but I don’t think that’s guaranteed. Really if you want to enforce a strict ordering, you need to tell Elm explicitly when the javascript-side command has finished.

1 Like

Elm has managed side-effects. These side-effects come in two flavours: Tasks and Cmds.

Tasks are side-effects that are guaranteed to finish and return either a success type or an error. Because of this, they can be sequenced using Task.andThen. This kind of imperative “do this and then do that” style is infrequently used in Elm and as such, most side-effects (like Http calls) are wrapped in Cmds . Cmds are “fire and forget”, they don’t have a guarantee of finishing. For example, you can call a port and never get back a reply. For this reason, Cmds cannot be chained with andThen like tasks.

5 Likes

So your problem is that you need to perform one of the manipulations first and then the other?

An alternative approach that comes to my mind is that you have a single port performActions and perform the two actions on the JS side. Then Elm only sends one message, and JS takes care of running the actions one after the other.

It’s not clear, if Elm needs to react to these changes, which would complicate things. Feel free to add more details, so we can maybe come up with a different approach to the problem.

Without really answering your question, when I have be in a situation where I care about the order of execution of things, I have taken a step back and thought about the domain requirements.

Say I need to do a call to a service and then use that result in another call. Then I would do the first call and when catching the result in my update, do the next if it was successful.

But if the outcome of one call is not the input to other, then you can just batch them. Unless you have some kind of side effects, then that is a problem in itself. Try not to have side effects.

To put it differently, if you need to execute things in a specific order, it also means that you need to check the outcome of each step. So any trick you do need to include a check of outcome in each step.

1 Like

It sounds like what you have is a simple linear (or mostly linear) state machine: at any given point, you’re in a particular state, and the way to transition to a succeeding state is by sending the appropriate message and receiving its reply. After receiving the reply, you send the next message in the sequence.

There are various ways to model a state machine in Elm. For simple stuff, I use a union type to represent the states (along with their associated data, where appropriate), and an update function that processes each reply and figures out what to do next. In a simple app, you could use the main update function; in more complex cases you could have per-module update functions that are delegated to by the main update.

For example, I have an app attached to a barcode scanner. One operation is to load a bin with some mix of tablets and phones (the bin, as well as the tablets and phones, are all labeled with barcodes). The process is pretty simple:

  • Scan a BEGIN LOAD BIN barcode.
  • Scan the bin barcode.
  • Scan a tablet or phone barcode.
  • Repeat the previous step for as many tablets and/or phones as there are.
  • Scan a END LOAD BIN barcode.

The corresponding state type looks like this:

type State
    = Idle
    | AwaitBin
    | AwaitTabletsOrPhones BinId (List TabletId) (List PhoneId)
    | Save BinId (List TabletId) (List PhoneId)

When in the Idle state, receiving the BeginLoadBin message (as a result of scanning the corresponding barcode) causes a transition to the AwaitBin state, and so on.

In this example, the only state that involves sending a new message as part of the processing is the Save ... state, which posts a SaveLoadBin message containing the bin data to the server and then waits for a ReceivedLoadBin response. But there’s no reason you couldn’t do that on one or more of the other state transitions.

It’s easy to detect invalid state transitions (for example, scanning the BEGIN LOAD BIN barcode and then a tablet or phone barcode without first scanning the bin’s barcode). In my case, I pop up an error notification alert and stay in the same state instead of transitioning.

4 Likes

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.