将 Http 请求与其余更新集成

Integrate Http requests with the rest of the updates

我正在使用以下模型制作一个简单的 Elm 应用程序:

type alias Model =
    { num : Float
    , str : String
    , list : List Float
    , serverResponse : String
    }

我正在关注 Todo-MVC 示例并且我有一个类似的架构:

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

我正在尝试添加一个按钮,该按钮将 POST 模型添加到某个页面,并在 return 中使用服务器的响应更新 model.serverResponse。但是我完全难住了。

谁能帮我填补这段代码中的空白:http://pastebin.com/1irNqh3S

简介

目前这比应该做的要难一些。下一个 Elm 版本 (0.15) 应该通过新的语言特性和对 HttpWebsocket 库的改造来解决这些尴尬问题。

基本问题是信号中的循环依赖性。您希望根据您的程序状态 ("the current model") 创建 HTTP 请求并根据 HTTP 响应更新程序状态。这应该是完全可能的,因为在两者之间有这种异步 HTTP 处理,而不是一些不可能的无意义的递归定义。

解决方法(破解):JavaScript 回显服务

但由于我们仍处于 Elm 0.14,我将向您展示一个解决方法。 请注意,这是一个危险的 hack!我将根据您提供的定义编写此代码,并且只在我重新定义的地方重复名称。评论解释了正在发生的事情。

榆树代码

-- 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代码

您应该有一个 HTML 文件,您可以在其中使用 Elm.fullscreenElm.embed 启动 Elm 程序。我假设您使用的是全屏版本:

// 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);
})

危险

我希望很明显,跳过这些圈圈是烦人和混乱的,而不是正常的 Elm 代码风格。我希望这足以阻止人们滥用它。我再说一遍,这将在即将推出的 Elm 0.15 中以更好的方式得到修复。

此方法的危险在于,您向 Elm 程序发送的事件多于您在 JavaScript 中获得的事件。这可能并不明显,这可能发生在这样一个简单的 JS 片段上,它会回应它得到的东西。但问题可能来自您的 Elm 程序。如果您的 Elm 程序针对它通过另一个端口获得的每个字符串从该端口发送一个 Http 响应字符串,并且(例如)在其他输入更改您的模型时重复该响应,那么您将开始累积得到回显的事件。通常 Elm 在事件同步方面可以很聪明,但是对于端口,所有的赌注都没有了,您可以通过累积事件使系统负担过重,从而使程序延迟和浏览器占用内存。所以请小心,不要将此技巧宣传为好东西。这只是权宜之计。

资源

  1. Ports
  2. 的文档
  3. Example project 用于使用端口
  4. 关于相同问题和解决方案的简短 mailing list discussion
  5. 一个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。请注意,他们必须在传出端口上使用 dropRepeats 以防止来自 JavaScript 的回显事件堆积。
  6. Tentative new APIs 对于 Elm 0.15 中的这些东西。