HTTP

It is often helpful to grab information from elsewhere on the internet.

For example, say we want to load the full text of Public Opinion by Walter Lippmann. Published in 1922, this book provides a historical perspective on the rise of mass media and its implications for democracy. For our purposes here, we will focus on how to use the elm/http package to get this book into our program!

Click the blue “Edit” button to look through this program in the online editor. You will probably see the screen say “Loading…” before the full book shows up. Click the blue button now!

Edit

  1. import Browser
  2. import Html exposing (Html, text, pre)
  3. import Http
  4. -- MAIN
  5. main =
  6. Browser.element
  7. { init = init
  8. , update = update
  9. , subscriptions = subscriptions
  10. , view = view
  11. }
  12. -- MODEL
  13. type Model
  14. = Failure
  15. | Loading
  16. | Success String
  17. init : () -> (Model, Cmd Msg)
  18. init _ =
  19. ( Loading
  20. , Http.get
  21. { url = "https://elm-lang.org/assets/public-opinion.txt"
  22. , expect = Http.expectString GotText
  23. }
  24. )
  25. -- UPDATE
  26. type Msg
  27. = GotText (Result Http.Error String)
  28. update : Msg -> Model -> (Model, Cmd Msg)
  29. update msg model =
  30. case msg of
  31. GotText result ->
  32. case result of
  33. Ok fullText ->
  34. (Success fullText, Cmd.none)
  35. Err _ ->
  36. (Failure, Cmd.none)
  37. -- SUBSCRIPTIONS
  38. subscriptions : Model -> Sub Msg
  39. subscriptions model =
  40. Sub.none
  41. -- VIEW
  42. view : Model -> Html Msg
  43. view model =
  44. case model of
  45. Failure ->
  46. text "I was unable to load your book."
  47. Loading ->
  48. text "Loading..."
  49. Success fullText ->
  50. pre [] [ text fullText ]

Some parts of this should be familiar from previous examples of The Elm Architecture. We still have a Model of our application. We still have an update that reacts to messages. We still have a view function that shows everything on screen.

The new parts extend the core pattern we saw before with some changes in init and update, and the addition of subscription.

init

The init function describes how to initialize our program:

  1. init : () -> (Model, Cmd Msg)
  2. init _ =
  3. ( Loading
  4. , Http.get
  5. { url = "https://elm-lang.org/assets/public-opinion.txt"
  6. , expect = Http.expectString GotText
  7. }
  8. )

Like always, we have to produce the initial Model, but now we are also producing some command of what we want to do immediately. That command will eventually produce a Msg that gets fed into the update function.

Our book website starts in the Loading state, and we want to GET the full text of our book. When making a GET request with Http.get, we specify the url of the data we want to fetch, and we specify what we expect that data to be. So in our case, the url is pointing at some data on the Elm website, and we expect it to be a big String we can show on screen.

The Http.expectString GotText line is saying a bit more than that we expect a String though. It is also saying that when we get a response, it should be turned into a GotText message:

  1. type Msg
  2. = GotText (Result Http.Error String)
  3. -- GotText (Ok "The Project Gutenberg EBook of ...")
  4. -- GotText (Err Http.NetworkError)
  5. -- GotText (Err (Http.BadStatus 404))

Notice that we are using the Result type from a couple sections back. This allows us to fully account for the possible failures in our update function. Speaking of update functions…

Note: If you are wondering why init is a function (and why we are ignoring the argument) we will talk about it in the upcoming chapter on JavaScript interop! (Preview: the argument lets us get information from JS on initialization.)

update

Our update function is returning a bit more information as well:

  1. update : Msg -> Model -> (Model, Cmd Msg)
  2. update msg model =
  3. case msg of
  4. GotText result ->
  5. case result of
  6. Ok fullText ->
  7. (Success fullText, Cmd.none)
  8. Err _ ->
  9. (Failure, Cmd.none)

Looking at the type signature, we see that we are not just returning an updated model. We are also producing a command of what we want Elm to do.

Moving on to the implementation, we pattern match on messages like normal. When a GotText message comes in, we inspect the Result of our HTTP request and update our model depending on whether it was a success or failure. The new part is that we also provide a command.

So in the case that we got the full text successfully, we say Cmd.none to indicate that there is no more work to do. We already got the full text!

And in the case that there was some error, we also say Cmd.none and just give up. The text of the book did not load. If we wanted to get fancier, we could pattern match on the Http.Error and retry the request if we got a timeout or something.

The point here is that however we decide to update our model, we are also free to issue new commands. I need more data! I want a random number! Etc.

subscription

The other new thing in this program is the subscription function. It lets you look at the Model and decide if you want to subscribe to certain information. In our example, we say Sub.none to indicate that we do not need to subscribe to anything, but we will soon see an example of a clock where we want to subscribe to the current time!

Summary

When we create a program with Browser.element, we set up a system like this:

HTTP - 图1

We get the ability to issue commands from init and update. This allows us to do things like make HTTP requests whenever we want. We also get the ability to subscribe to interesting information. (We will see an example of subscriptions later!)