tl;dr
Tired of writing so many ports and subscriptions to request and retrieve values from JavaScript, I made a package that just automates the whole thing internally so you dont have to wire up any incoming or outgoing ports. Its called Chadtech/mail, and you can see an example project here. Below is the most basic example code:
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
This code is saying "Pass along this Json.Encode.Value, to the javascript function named "login", and decode its response value with loginDecoder, which will be handled by the Msg LoginResult". Thats it. Theres no outgoing port that needs to get called, and you dont need to set up the subscription to listen for the result.
Background
@splodingsocks gave that great talk on ports at Elm conf, where he showed everyone a code technique of having only one incoming and outgoing port. Its a really great idea that excited a whole lot of us. At my job, we ended up implementing it in our Elm apps. @pdamoc made Elm Ports Driver, which are an Elm and Npm package for this technique too.
I ended up talking about it with enough people that I wrote this gist, summarizing how I do it. I ended up talking to James Hopkins, a new member of the Elm slack, about his project using ports to access firebase. Somehow an idea emerged between us in our conversation:
What if ports were like http requests, and you didnt have to wire up any subscriptions at all? Instead you could just pack up an address, a value, and an expected return value and send it off.
So thats what I set out to make, and thats what Chadtech/mail is. The whole one-incoming-and-outgoing-port thing is still there, but its entirely encapsulated inside this new Mail.Program type. Its all automated behind the scenes. On the JavaScript side you have functions with addresss, and you can send Letters to those addresses just by passing along a String with a Json.Encode.Value. The JS function also gets a call back, and if you mailed your Letter along with a Decoder a and a Result String a -> msg, Chadtech/mail decodes the return value and passes the Msg right into your update function.
The whole thing ended up being really intensive
I didnt realize this at the onset, but to make this work I had to kind of overhaul everything. You know how theres Html.program, but separately theres also Navigation.program? I had to make a Mail.program. The most fundamental change from Html.programs is that Mail.Program update functions return (Model, Mail Msg) instead of (Model, Cmd Msg). Its just an extension of Cmds, regular old Cmd Msgs can become Mail Msgs by means of Mail.cmd : Cmd Msg -> Mail Msg and they work just how they did before. Thats the most fundamental difference.
Thats my project. Im curious for any feedback, or if you think this might be useful. I was really just trying to get a proof of concept together but now I have some hope this could actually be practical. Let me know if you dont think so. Seeing as this was a lot more of a fundamental change in the Elm architecture than I thought it would be, it seems to me that the best implementation of this would be in the language itself, and not as a package. It really would just be making a Ports.send : String -> Letter msg -> Cmd msg function, along with a callback inside app.ports.f.subscribe functions.