Expected knowledge: Basics of Elm and the Elm Architecture

Read to learn: How to handle HTTP errors in Elm

I remember one of the things I struggled with writing my first Elm program (other than writing a JSON decoder) was dealing with non-successful HTTP responses. I've heard the question asked a few times in the Elm Slack channel and decided that I would share how I solved the problem. I'll assume that you're following the Elm Architecture.

Status Quo

Let's start by looking at the signature of the Http.get function:

get : Decoder value -> String -> Task Error value
view raw http.elm hosted with ❤ by GitHub

What we need out of this is an Effects Action to return in our update function. Here's how things might look to start with:

type Response
= ...
type Action
= Updated (Maybe Response)
decoder : Decoder Response -- implementation elided
url : String -- ditto
update : Action -> Model -> ( Model, Effects Action )
update action model =
case action of
Updated maybeResponse ->
case maybeResponse of
Just response -> -- do things
Nothing -> -- /shrug
requestUpdate : Effects Action
requestUpdate =
Http.get decoder url -- Task Error Response
|> Task.toMaybe -- Task never (Maybe Response)
|> Task.map Updated -- Task never Action
|> Effects.task -- Effects Action
view raw basics.elm hosted with ❤ by GitHub

This works great except that we throw away information about the failure case. We either get a Just Response or Nothing!

Retaining Error Information

Let's see how we can do better. First, we need to change our Updated action to account for errors by accepting a Result that can contain either an Error or a Response:

type Action
= Updated (Result Error Response)
view raw action.elm hosted with ❤ by GitHub

Then we can update our request function to send the errors along as well:

requestUpdate : Effects Action
requestUpdate =
Http.get decoder url -- Task Error Response
|> Task.toResult -- Task never (Result Error Response)
|> Task.map Updated -- Task never Action
|> Effects.task -- Effects Action
view raw get.elm hosted with ❤ by GitHub

Our update function will need to change as well to accomodate the extra information we're passing along:

update : Action -> Model -> ( Model, Effects Action )
update action model =
case action of
Updated result ->
case result of
Ok response -> -- do things
Err error -> -- handle error
view raw update.elm hosted with ❤ by GitHub

This is a big step forward! Now we can update our UI to reflect the fact that something's gone wrong or log the failure to the console.

An Alternative Approach

If we want, though, we can also have a generic error action that we can reuse. Here's what that approach would look like:

type Action
= Updated Response
| APIError Error
update : Action -> Model -> ( Model, Effects Action )
update action model =
case action of
Updated response -> -- do things
APIError error ->
let
_ =
case error of
Http.Timeout ->
Debug.log "API timeout" error
Http.NetworkError ->
Debug.log "Network error contacting API" error
Http.UnexpectedPayload payload ->
Debug.log ("Unexpected payload from API: " ++ payload) error
Http.BadResponse status payload ->
Debug.log ("Unexpected status/payload from API: " ++ (toString status) ++ "/" ++ payload) error
in
( model, Effects.none )
requestUpdate : Effects Action
requestUpdate =
Http.get decoder url
|> Task.map Updated
|> (flip Task.onError) (Task.succeed << APIError)
|> Effects.task
view raw alternative.elm hosted with ❤ by GitHub

Of course, instead of just logging the errors to the console we can show the user an error state, retry, or anything else! I hope this helps but if not, ping me on the Elm slack channel.