2

I am trying to build a simple client-server system with Clojure (server) and ClojureScript (client).

The server side is OK (everything is green on the browser's console, no CORS problems, etc.); the response is plain text ("true" or "false"). On the client side I am using basically the function included in the official cljs-http website

(defn get-request [str1 str2]
(go (let [response (<! (http/get "http://localhost:3000"
                                 {:query-params {"str1" str1
                                                 "str2" str2}}))]
  (prn response))))

Running this function on the REPL indicates that everything is fine and something map-like comes out of the async channel

cljs.user=> (b/get-request "foo" "bar")
#object[cljs.core.async.impl.channels.ManyToManyChannel]         ;; the result
cljs.user=> {:status 200, :success true, :body "false", [...]}   ;; output of `prn`

My question is: How can I actually get the response's body ouf of that ManyToManyChannel "object"?

  • For the sake of testing, changing the function's last expression to (prn (:body response)) prints "false" but still returns theManyToManyChannel
  • Changing the function's last expression to just (:body response) surprisingly returns the same instead of "false".

How can I proceed?

Jorge D
  • 41
  • 7
  • Your function is returning the result of the go macro, not what's inside it. – Jared Smith Jun 05 '21 at 10:52
  • @JaredSmith: I know - and inside that result is the `:body "false"` entry. The question is how can I fetch that entry? – Jorge D Jun 05 '21 at 10:57
  • By [consuming the returned channel](https://clojuredocs.org/clojure.core.async/take!) in the calling code. There's no way to turn an async operation into a sync one, that's kinda the point. – Jared Smith Jun 05 '21 at 10:57
  • @JaredSmith: I tried that but `take!` is available for Clojure only and my client runs on ClojureScript. Am I overlooking something? Thank you – Jorge D Jun 05 '21 at 11:09
  • Ah, wrong link my bad. You want <!. Hang on a sec. – Jared Smith Jun 05 '21 at 11:13

2 Answers2

2

You cannot access the result of an async operation (eg. go) in a sync fashion. So the only way to get the actual response is in the async callback.

Getting the result in the REPL is a little tricky. You can store it in an atom and access it from the atom once the operation completes.

(def result-ref (atom nil))

(defn into-ref [chan]
  (go (reset! result-ref (<! chan))))

(into-ref
  (http/get "http://localhost:3000"
    {:query-params
     {"str1" str1
      "str2" str2}}))

@result-ref

Of course you can only access the result once the operation has actually completed. The REPL is a bit tricky for async operations but this trick might help.

Thomas Heller
  • 3,842
  • 1
  • 9
  • 9
  • 1
    Polling a ref is *extremely* error-prone compared to just putting the response value back in a channel. – Jared Smith Jun 05 '21 at 22:11
  • 2
    All this example demonstrates is how you can get a value from a channel so you can interact with it in the REPL. I did not say anything about polling repeatedly or anything of that sort. – Thomas Heller Jun 05 '21 at 23:10
  • your approach works (using an atom to save the result) but I notice some timing problems (reading before writing) - maybe @Jared Smith is right... Anyway, I will try both approaches – Jorge D Jun 05 '21 at 23:18
  • 2
    This is **ONLY** meant for exploration in the REPL. Do **NOT** do this in regular code ever! – Thomas Heller Jun 05 '21 at 23:19
  • @ThomasHeller fair, I inferred that. But I think people not used to dealing with asynchronicity will take it to mean that's how you do it in actual code. I interpreted the question to be more than just about how to interact with channels at the REPL. – Jared Smith Jun 06 '21 at 13:48
1

It's Saturday morning and I'm feeling lazy so I didn't run this, but this should work:

(defn get-request [str1 str2]
  (go (<! (http/get "http://localhost:3000"
                    {:query-params {"str1" str1
                                    "str2" str2}}))))


(defn read-response [response-chan]
  (go (let [resp (<! response-chan)]
        (prn resp))))  ; <- the only place where you can "touch" resp!

(read-response (get-request "a" "b"))

The go block returns a channel that will receive the result you put into it in the request response (the returned form from the go block in your original).

The read function takes a channel and just reads from it, the last form just calls the second function with the result of calling the first.

Remember that channels are first-class constructs, you can pass them around, share them with multiple consumers, etc.

Jorge D
  • 41
  • 7
Jared Smith
  • 19,721
  • 5
  • 45
  • 83
  • Sorry but I want to _query_ the response, not print it. Assuming that `resp` contains all the data (after being extracted once or twice from some channel), at the end of the day I want to run something like `(:body resp)` so that I have the string "true" or "false" for comparing. In other words: I want to _work_ with the response, not just displaying it with `prn` – Jorge D Jun 05 '21 at 14:26
  • 1
    @JorgeD ...and I just provided you a recipe for doing so. You don't have to just print it, you can do anything you want with it. The difference between my answer and the code in your question is I showed you how to extract the response data in an arbitrary function rather than just within the go block where you initiated the HTTP request itself. What you seem to want, and what you cannot do, is turn the response from the HTTP request into a *synchronous* result (ie return `(:body resp)`). Once you go async, *everything* is async, because returns are immediate but the value isn't ready yet. – Jared Smith Jun 05 '21 at 22:09
  • 2
    I realize it's a bit weird coming from a Java background, but the first rule of Javascript is that you don't block. The second rule of Javascript *is that you don't block*. Because there's only one thread, and if you block then your UI is completely unresponsive. The only way to do what you're trying to do would be to block a thread and wait, and there's only the one thread to block, and the Clojurescript compiler (wisely, correctly) will not let you do that, it's why the blocking variants in core.async (`<!!`, `take!`, etc) are missing in Clojurescript. – Jared Smith Jun 05 '21 at 22:14
  • See also https://stackoverflow.com/questions/14220321/how-to-return-the-response-from-an-asynchronous-call – Jared Smith Jun 05 '21 at 22:17
  • 1
    First of all, thanks a lot for your help. It is a tricky situation * I tried your suggestion on the REPL and it did not work - probably because the REPL and async are hard to handle as @thomas-heller wrote ... * ...therefore I tried his solution (with an atom) and it works but I have some timing problems -- you may be right re that approach being error-prone * ...therefore I will try your solution later today (it's 1a.m. here) not in the REPL but inside the program – Jorge D Jun 05 '21 at 23:15
  • @JorgeD glad to help! – Jared Smith Jun 06 '21 at 13:48