Slightly more advanced guide to making HTTP calls in Elm
I’ve covered some basics of making HTTP calls from Elm in my previous article. These are also covered in the official guide, over here and here. This article pulls all this information together and goes into a little more depth. We’ll cover:
- What to expect with HTTP calls
- How to handle errors in HTTP calls
- How to track the progress of HTTP calls
What to expect with HTTP calls
As you might have seen in the guide and the package’s documentation, there are multiple ways to make HTTP calls within Elm. The get
function's the simplest, requiring only an address and an argument specifying what is being expected. Let's start there.
The get
function of the Http
module takes a record as an argument and returns a message. We give the url
field the full path of the resource (the API endpoint or the file, for instance). Simple enough. The expect
field, though, says it requires to be given an Expect msg
type. Looking through the Http
module's documentation, we see that there are four functions that return an Expect msg
. Here are their signatures:
expectWhatever : (Result Error () -> msg) -> Expect msg
expectString : (Result Error String -> msg) -> Expect msg
expectJson : (Result Error a -> msg) -> Decoder a -> Expect msg
expectBytes : (Result Error a -> msg) -> Decoder a -> Expect msg
If we have no use for what the server sends back, pick expectWhatever
. Otherwise, we can pick one of the others depending on what we expect the server to send.
Note: I won’t be covering
expectBytes
just yet.
When we expect strings
If it’s a String
we are expecting from the server, of course, pick expectString
and give it a Msg
to trigger when the server responds. Like so:
Notice that GotSomeText
was not sent a String
but a Result
. This is because many things can go wrong when talking to servers. More on that later.
When we expect JSON
The server may be sending you JSON. No problem. Use expectJSON
and supply it with a JSON decoder to parse the data and a message to trigger. Like so:
So, what’s happening here?
- On line 17, at the start of the call, we are letting
Http.request
know that we are expecting JSON, and that when the data comes in, it has to be parsed with thereportDecoder
. - Lines 36 and 37 of
reportDecoder
are more or less self-explanatory -- look for a field named so-and-so and turn it's value into an Elm type of such-and-such. Most of the time, you'll probably be in good stead knowing this much. However, the third line pushes out the comfort zone a tad bit more. But, because of the amazing pipe operator (|>
) you can sense that it's just a two step instruction to Elm: (1) Look for a field named “uploadedOn” in the JSON and convert it's value into anint
. (2) Take thatint
value and then send it to a function calledmillisToPosix
of a module calledTime
. (Don't worry about the details right now; we are just learning to articulate the instructions we are giving to Elm. All good?) - Whether the data can be decoded or not, a
Result
will be produced. So, we handle both, the success as well as the failure scenarios, in theFetchedAReport
section (line 25) ofupdate
.
How to handle errors in HTTP calls
Of course, there are many points of failure in making http
calls: the wifi might be down, the server might be on the blitz, the JSON might have changed, etc. Errors may occur. They have to be handled.
Take a look at the two messages that handle what happens after an http
call again:
GotSomeText (Result Error String)
FetchedAReport (Result Error Report)
Both are expecting that an error might occur. That’s why they have a Result
as their associated data. The Result
has two variants and might take the form of Ok x
or Err y
. That's why we have to pattern match in the GotSomeText result
branch, for instance, under update
.
If everything worked as expected, great. The first branch’s code (line 3) will get executed. However, in case an error occurred, the second branch (line 5) executes. The errors that can occur are described in the documentation, under Http.Error and summarised in the code snippet below.
One point to note: if Elm has trouble parsing the JSON, it’ll result inBadBody
errors. You may want to use Debug.log
and Debug.toString
to output those errors to the console to figure out what went wrong. Like so:
The Debug.log
(line 18) outputs the badBodyError
to the console. Read it to find out where the problem in the JSON is.
How to track the progress of HTTP calls
There didn’t seem to be much information on this aspect when I was learning about HTTP calls. Don’t know if it was too simple to be explained or if people didn’t use the feature enough. Either way, here’s what I learned about tracking the progress of HTTP calls.
If an HTTP call is taking a long time to complete — suppose you are downloading a large file, or uploading a lot of data — you may want to report progress to the user so that they aren’t left thinking that something broke. To do this, you will need to track the progress of your HTTP calls.
There are three steps to tracking progress of HTTP calls:
- Step 1: Assign an id to the call that you want to track
- Step 2: Let Elm know we want to know the progress of those calls
- Step 3: Handle the updates
Step 1: Assign an id to the call that you want to track
The Elm Runtime reports progress of HTTP calls, and you can listen for them by subscribing to them. Since there may be more than one call progressing at a given time (maybe multiple files are being downloaded), you will need to give each HTTP call an ID. You cannot do this with the standard Http.get
or Http.post
functions; you will have to use a custom Http.request
. Here’s its format:
Source: https://package.elm-lang.org/packages/elm/http/latest/Http#request
See that tracker
bit on line 8? That's where we give the call an ID.
Supposing you had two HTTP calls that you were tracking, this is how you'd subscribe to get notified of each of their progress:
Step 2: Let Elm know we want to know the progress of those calls
Now, we need to let the Elm runtime know that we are interested in receiving updates to the progress of those calls. We can do that in subscriptions, using the IDs we just used. Like so:
As you can see on lines 9 and 10, the IDs we used in Step 1 correspond to the ones in the subscriptions
. Now, if the Elm runtime reports progress for the call with the ID "callOne"
, the GotProgressOfCallOne
message is triggered and if the Elm runtime reports progress for the call with the ID "callTwo"
, the GotProgressOfCallTwo
message is triggered.
Step 3: Handle the updates
Now, all we have to do is handle the message when it gets called, in update
. Like so:
You’d have noticed progress
. It's of the Http.Progress
type, which contains details about the transfer, whether outgoing or incoming. Here is the Progress
type:
Source: https://package.elm-lang.org/packages/elm/http/latest/Http#Progress
Conclusion
That’s it. You’re all set. You have most of what you need to work with HTTP calls in Elm.
Do you have questions? Would you like me to explain something else? Let me know in the comments.
Have fun!
If you’d like, you can support my caffeine habit with a virtual coffee!