问题
I am making a simple Elm application with the following model:
type alias Model =
{ num : Float
, str : String
, list : List Float
, serverResponse : String
}
I am following the Todo-MVC example and I have a similar architecture:
type Action
= NoOp
| ChangeNum String
| ChangeStr String
...
view : Model -> Html
view model =
...
update : Action -> Model -> Model
update action model =
case action of
...
main : Signal Html
main = Signal.map view model
model : Signal Model
model = Signal.foldp update initialModel (Signal.subscribe updates)
initialModel : Model
initialModel =
...
updates : Signal.Channel Action
updates = Signal.channel NoOp
I am trying to add a button that will POST the model to some page and in return update model.serverResponse with the server's response. But I am completely stumped.
Can someone help me fill the gap in this code: http://pastebin.com/1irNqh3S
回答1:
Intro
This is currently a little harder to do than it should be. The next Elm release (0.15) should take care of the awkwardness with a new language feature and a revamp of the Http and Websocket libraries among other things.
The basic problem is a cyclic dependency in your signals. You want to create HTTP requests based on your program state ("the current model") and update the program state based on the HTTP responses. This should totally be possible because there is this asynchronous HTTP handling in between, not some senseless recursive definition that cannot be.
Workaround (hack): JavaScript echo service
But since we're still at Elm 0.14, I'll show you a workaround. Please note that this is a dangerous hack! I'll base this code on the definitions you gave and only repeat names where I redefine. The comments explain what's happening.
Elm code
-- These are the Http response strings, but coming from JavaScript through a port
port asyncResponses : Signal String
responseActions : Signal Action
responseActions = responseToAction <~ asyncResponses
-- The order of these two parameters of merge may matter. Check which should take precedence.
input : Signal Action
input = Signal.merge responseActions (Signal.subscribe updates)
-- note the redefinition:
main : Signal Html
main = Signal.foldp update initialModel input
-- These are the same Http response strings, but we're sending them out so JavaScript can listen to them.
port responses : Signal String
port responses = Http.send requests |> Signal.keepIf isSuccess (Success "") |> Signal.map (\Success s -> s)
isSuccess response = case response of
Success _ -> True
_ -> False
JS code
You should have an HTML file in which you kick off the Elm program with Elm.fullscreen or Elm.embed. I'm going to presume you use the fullscreen version:
// Catching the returned object from Elm.fullscreen:
var program = Elm.fullscreen(Elm.Main, {asyncResponses : ""})
// JS Echo Service:
program.ports.responses.subscribe(function(s) {
program.ports.asyncResponses.send(s);
})
Dangers
I hope it's obvious that jumping through these hoops is annoying and messy and not normal Elm code style. And I hope that's enough to discourage people from abusing this. And I repeat, this is going to be fixed in a nicer way in the upcoming Elm 0.15.
The dangers of this method are that you send more events to the Elm program than you get in JavaScript. It may be non-obvious that this can happen to a such a simple piece of JS that echoes what it gets. But the problem may come from your Elm program. If your Elm program sends an Http response string out of the port for every string it gets through the other port, and also (for example) repeats that response when some other input changes your model, then you start to accumulate events that get echoed. Normally Elm can be clever about event synchronisation but with ports all bets are off and you can overtax the system with accumulating events that make the program lag and the browser hog memory. So please be careful, and don't advertise this trick as a good thing. It's only a stopgap.
Resources
- Documentation of Ports
- Example project for using ports
- A short mailing list discussion about the same problem and solution.
- An example Elm game from the ludum dare mini, which uses the same technique for playing and stopping audio. I explained this solution to one of the creators on the #elm IRC channel. Note they had to use a
dropRepeatson the outgoing port to keep the echoed events from JavaScript from piling up. - Tentative new APIs for these kind of things in Elm 0.15.
来源:https://stackoverflow.com/questions/28267085/integrate-http-requests-with-the-rest-of-the-updates