Clojure All The Way - The Client Part 2 of mini-series
The previous post dealt with the Server part, now let’s look at the Client side.
And we have a Client capable of calling this API and receiving data, but it still treats it as text, and not as structured Clojure data. The client was actually explained not in the last post, but in post before that.
First, small improvement to GET function
Note: Code samples include only interesting parts, full source code is available below.
GET function in previous client post was implemented using
(>! ch ...) inside
go-routine (line 6):
1 (defn GET [url] 2 (let [ch (chan 1)] 3 (xhr/send url 4 (fn [event] 5 (let [res (-> event .-target .getResponseText)] 6 (go (>! ch res) 7 (close! ch))))) 8 ch))
In comments to that post Alexander Solovyov suggested using
put! instead. It’s a good idea (thanks Alexander!) since we don’t care when channel write completes. Here is the change:
1 (defn GET [url] 2 (let [ch (chan 1)] 3 (xhr/send url 4 (fn [event] 5 (put! ch (-> event .-target .getResponseText)) 6 (close! ch))) 7 ch))
go blocks are gone and we use
put! in line 5 instead.
Now back to our main topic …
Getting Clojure data from Server response
Let’s add a new function
get-edn that uses
GET function above to get data from the Server, and then converts it to Clojure data structures, i.e. edn-decodes it. It is actually very simple, all we need to do is call
read-string function from
And in addition to being useful for our purposes it will also show how async functions compose.
1 (defn get-edn [url] 2 (go 3 (-> (GET url) 4 <! 5 read-string)))
Let’s look at it from inside out. In line 3 we call our
GET that returns a
channel that will eventually contain result (text). In line 4 we read the value from this
channel potentially “parking” this activity, but not blocking. This is why we need this code wrapped in
go-routine. And then in line 5 we pass returned text to
read-string that edn-decodes it and returns Clojure data structure (let’s call this value
But what happens next? How is
result propagated to the caller of
get-edn? To answer that we need to understand what
get-edn returns. Following normal Clojure rules
get-edn returns the value returned by
go form. This is a
channel, now who writes to this
go form does. Here is how
- first it creates a
channeland promptly returns it to the caller
- eventually it evaluates all statement inside, potentially “parking” this activity when
>!forms are encountered
- it writes the value of the last statement to the
In our case the last and only statement inside
go is our “conveyor” producing
result defined two paragraphs ago, i.e. decoded response. The
go form writes
result to the
channel it has created and this is how it gets to
And this is how async functions compose! Notice that we didn’t even have to create a
channel explicitly in
The reason we had to create
GET function is because we were converting callback-based API to asynchronous one. The caller of our
fn in there was not async-aware, and actually ignored the return value. So we had to propagate it to
GET caller thru manually-created
Calling get-edn function
To prove to ourselves that
get-edn returns Clojure map let’s call some function on it before displaying it, for example let’s extract only
1 (go 2 (dom/set-text! (sel1 :#log) 3 (-> (get-edn "/api/echo") 4 <! 5 :headers))
The whole block is wrapped in
go routine because we use
<! (read channel) form. We call
get-edn in line 3 that returns a channel, we read from it in line 4, potentially “parking” (but again, not blocking) this activity. Then we call
:headers in line 5 to get only headers portion of respose proving that result is actually a Clojure map.
And if you run the sample code, you’ll see the headers displayed.
Now this is nice, but the way they are displayed is kind of lame. We’ll do something about it in the next post, while staying on topic of using Clojure data structures as much as possible.
Full source code can be found on GitHub.
If you want to build and run it locally, execute:
git clone https://github.com/Dimagog/dimagog.github.io.git -b ClojureAllTheWay2 --single-branch ClojureAllTheWay2 cd ClojureAllTheWay2 lein ring server
blog comments powered by Disqus