Receive message from an Elm process

Viewed 116

I'm toying around with Elm processes in order to learn more about how they work. In parts of this, I'm trying to implement a timer.

I bumped into an obstacle, however: I can't find a way to access the result of a process' task in the rest of the code.

For a second, I hoped that if I make the task resolve with a Cmd, the Elm runtime would be kind enough to perform that effect for me, but that was a naive idea:

type Msg
  = Spawned Process.Id
  | TimeIsUp

init _ =
  ( Nothing
  , Task.perform Spawned (Process.spawn backgroundTask)
  )

backgroundTask : Task.Task y (Platform.Cmd.Cmd Msg)
backgroundTask =
  Process.sleep 1000
    -- pathetic attempt to send a Msg starts here
    |> Task.map ( always
                  <| Task.perform (always TimeIsUp)
                  <| Task.succeed ()
                )
    -- and ends here
    |> Task.map (Debug.log "Timer finished") -- logs "Timer finished: <internals>"

update msg state =
  case msg of
    Spawned id ->
      (Just id, Cmd.none)

    TimeIsUp ->
      (Nothing, Cmd.none)

view state =
  case state of
    Just id ->
      text "Running"

    Nothing ->
      text "Time is up"

The docs say

there is no public API for processes to communicate with each other.

I'm not sure if that implies that a process can't cummunicate with the rest of the app.

Is there any way to have update function receive a TimeIsUp once the process exits?

2 Answers

There is one way but it requires a port of hell:

  1. make a fake HTTP request from the process,
  2. then intercept it via JavaScript
  3. and pass it back to Elm.
port ofHell : (() -> msg) -> Sub msg

subscriptions _ =
  ofHell (always TimeIsUp)

backgroundTask : Task.Task y (Http.Response String)
backgroundTask =
  Process.sleep 1000
    -- nasty hack starts here 
    |> Task.andThen ( always
                      <| Http.task { method = "EVIL"
                                   , headers = []
                                   , url = ""
                                   , body = Http.emptyBody
                                   , resolver = Http.stringResolver (always Ok "")
                                   , timeout = Nothing 
                                   }
                    )

Under the hood, Http.task invokes new XMLHttpRequest(), so we can intercept it by redefining that constructor.

<script src="elm-app.js"></script>
<div id=hack></div>
<script>
  var app = Elm.Hack.init({
    node: document.getElementById('hack')
  })

  var orig = window.XMLHttpRequest
  window.XMLHttpRequest = function () {
    var req = new orig()
    var orig = req.open
    req.open = function (method) {
      if (method == 'EVIL') {
        app.ports.ofHell.send(null)
      }
      return orig.open.apply(this, arguments)
    }
    return req
  }
</script>

The solution is not production ready, but it does let you continue playing around with Elm processes.

Elm Processes aren't a fully fledged API at the moment. It's not possible to do what you want with the Process library on its own.

See the notes in the docs for Process.spawn:

Note: This creates a relatively restricted kind of Process because it cannot receive any messages. More flexibility for user-defined processes will come in a later release!

and the whole Future Plans section, eg.:

Right now, this library is pretty sparse. For example, there is no public API for processes to communicate with each other.

Related