Fork me on GitHub
#core-async
<
2017-10-29
>
xiongtx03:10:36

I'm 😕 about how to use cljs.http's channels w/ reagent components. I've a component, video-list, that needs to make a YouTube API call, get the reply, and create a list (for the sake of simplicity, let's say it's a list of video ID). Below doesn't work b/c go blocks return a channel:

(defn video-list
  []
  (go (let [{:keys [status body]} (<! (http/get ""
                                                {:query-params {"key" api-key
                                                                "q" "surfboards"
                                                                "part" "snippet"
                                                                "type" "video"}}))]
        `[:ul
          (when (= 200 status)
            ~@(for [item (:items body)]
                [:li (get-in item [:id :videoId])]))])))
So what's the right way to go about this?

mingp04:10:01

How does Clojure's core.async programming model compare and contrast with Go's goroutine programming model? Are they largely capable of the same workflows using the same techniques? Is there any sort of guide for translating from one model to the other? That is to say, if I know how to do XYZ in Go using goroutines: (1) Will I be able to do the same thing in Clojure using core.async? and (2) How can I learn to do that? Thanks in advance.

mpenet11:10:48

It s very similar. A few ops that panic in golang are noop in clojure (closing a closed chan). And maybe knowing when to use async/thread vs go depending on what the code does (nio vs blocking io). Then some diffs in what s considered idiomatic in both langs but that s minor

mpenet11:10:14

Best advice would be try to get a feel for it in the repl, it s much easier to do in clj ;)

mingp13:10:08

Sounds like a good idea. My only concern is that, with Go and goroutines, it seems like a hybrid channels + mutexes/condvars style is at least allowed and possibly even encouraged. While Clojure does have mutexes/condvars as inherited from Java, they use real threads to implement blocking semantics, so it seems like they would be incompatible with core.async channels.

mpenet15:10:42

Channels are just a conduit, nothing prevents you to feed them from outside go blocks

mpenet15:10:13

It s quite common when doing interop with java libs doing async in whatever ways

mingp15:10:05

Right, but they can't hook into the core.async parking model, AFAIK. That's where I'm getting stuck. The main advantage in Go is that all of the standard library works around goroutines.

mingp15:10:25

In some sense, where I'm getting stuck is that, core.async is (intentionally by design) not a generic coroutines library.

jsa-aerial17:10:54

If you use the single '!' versions of things (>! instead of >!!, <! instead of <!!, etc) then you are basically in coroutines land - in the sense that using these in a go block will park the processing of the current thread. This thread may then be switched (if possible) to a previously parked piece of code if it is 'ready' and its execution will be on this thread until another parking action.

jsa-aerial17:10:46

OTOH, if you do a form of blocking io inside a go block the thread will not park but block and it won't be activated again until the blocking releases (io satisfied or whatever)

jsa-aerial17:10:41

So, never do blocking actions in a go block. In JS land, you only have the single '!' variants to work with as there is only one thread

mingp17:10:34

Is there some way to make this mix correctly with traditional threading primitives? Use ! versions internally and !! versions at the boundary layer?

jsa-aerial19:10:22

Oh sure - in JVM (Clj not Cljs) use the '!!' variants with threading (outside go blocks) and thin will work fine. And you can use this alongside go blocks and '!' variants which will park threads from the async pool. But be careful not to mix these modes up.

noisesmith07:10:43

@xiongtx in my experience with reagent the best bet is to use an r/atom to deliver the data from a go block to the component that should render it

xiongtx17:10:36

I'm trying:

(let [result (r/atom nil)]
    (go (let [response (<! (http/get "https://www.googleapis.com/youtube/v3/search"
                                     {:query-params {"key" api-key
                                                     "q" "surfboards"
                                                     "part" "snippet"
                                                     "type" "video"}}))]
          (reset! result response)))
    (let [{:keys [status body]} @result]
      [:ul {:class "col-md-4 list-group"}
       (when (= 200 status)
         (for [item (:items body)]
           [:li (get-in item [:id :videoId])]))]))
But it's not clear when the go block will deliver to the atom. A simpler example:
(let [result (r/atom nil)]
  (go (reset! result "foobar"))
  @result)
;; => nil

noisesmith18:10:10

right, the typical usage of an r/atom is that it causes the component to re-render as soon as new data is available

noisesmith18:10:37

so instead of waiting on the result, then rendering, you first render an empty container, and later it has thecontents in it

xiongtx02:10:34

👌, I guess I'm don't have a great understanding of under what conditions re-rendering occurs. Defining result as an r/atom outside of video-list seems to work, but defining it in a let-binding inside video-list doesn't. Are there any guidelines as to how to manage these atoms and when they're derefed (I know there exist frameworks like re-frame for this, but I'm talking specifically about reagent).

xiongtx03:10:06

Ah, seems like I didn't create a proper closure around the let-bound r/atom. But the general issue of best practices around state and derefs stands.