Port request/response pattern?

Is there a package that implements a request/response pattern for use with pairs of ports? In such a way, that when making the request you can specify what Msg to give on the response?

So it would work a bit like Task.attempt:

attempt : (Result x a -> msg) -> Task x a -> Cmd msg

There when running a Task, I supply a function telling attempt what message to build and pass to the update function, up-front.

The problem with ports, is that if you have a pair of ports, you must listen for the response as a subscription. For example:

port saveToLocalStorage : Value -> Cmd msg
port localStorageResult : (Value -> msg) -> Sub msg

This localStorageResult port knows nothing about the request that it is a response to. For example, I might like to save something to local storage as part of a chain of operations, so I need to know when it has saved succesfully, and then continue with the next operation after that. The same saveToLocalStorage operation could be used in many different chains of operations.

I tell the subscription what message to generate, but what I really want is to tell the outgoing port call what message I want it to respond with.

I have implemented this pattern, as described below, but on elm-serverless which has additional complications, since it must also track multiple connections. Just wondering if there is already a package which implemets this pattern in a more minimal way for regular elm applications?

It does mean having to hold a context in your Model, and also putting functions in your Model, to do this.

===

I have implemented this in [Serverless - elm-serverless 4.0.0]. Since elm-serverless has a context record called Conn, there was already a convenient place to hold the context.

type alias Conn msg =
    { currentSeqNo : Int
    , context : Dict (Int, Value -> msg)
    , ...
    }

When making a port call, I bump to a unique sequence number. Against the unique sequence number, I hold a Value -> msg function. When the response comes back in the subscription, the context is looked up in the Dict, a message is generated from the response, and then update is called with that message.

1 Like

I found this package recently, which is very interesting:

https://elm.dmy.fr/packages/brian-watkins/elm-procedure/latest/

It allows you to build Procedures, which are a unification over Cmd, Task and Sub, with composition built in. So if I understand your question correctly, it would be able to model that just fine.

2 Likes

Quite a lot to get your head around there, but yes, this looks like it will do the job! :thinking:

Who needs “task ports” anway…

I’ve used elm-porter 3.0.0 in the past to implement a Shadow-DOM compatible version of Browser.Dom, it worked pretty well

1 Like

I have a solution for this whenever I want to interact with some external JS api in a nice way.

You can see an example of it here:

The general pattern lets you perform a sequence of actions on the js side, and interweave Expects whenever you want to get the result of a computation. I keep experimenting with slightly different APIs but the general concept is always the same. Might be simpler than elm-procedure, not sure.

1 Like

You can also create service workers and in Elm use plain HTTP requests. the advantage of this is that you have timeout handling, progress handling and error handling, all out of the box.

4 Likes

Ahhh this is clever. Doesn’t work for backend elm shenanigans which elm-serverless is, though.

Yeah, not after the service worker trick anyway, was really looking for what pure Elm solutions there are.

I made this package for this purpose in 0.18, and I just havent upgraded it to 0.19: mail 1.0.1 I had exactly that situation in mind that you mentioned @rupert regarding the pairs of ports.

In chadtech/mail I made an api that is similar to how http works, where when you send a port, you also provide the decoder for the reply you are listening for.

Heres the code example from the readme.

mailLogin : Model -> Mail Msg
mailLogin model =
    [ ( "username", Encode.string model.username )
    , ( "password", Encode.string model.password )
    ]
        |> Encode.object
        |> Mail.letter "login"
        |> Mail.expectResponse loginDecoder LoginResult
        |> Mail.send

You send out the payload to JS, and you provide the Msg and Decoder a for how the response should be handled. And Mail msg is the chadtech/mail alternative of a Cmd msg. update functions in this system should return (Model, Mail msg) instead.

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