core-async 2025-09-12

So after revisiting some (not all!) core async material, my revised, hopefully entertaining (I hate to be boring), and respectful (i have have flown to far off the handle last time around), take on core async on the browser is this: It's nice if your planning on sharing your logic across different physical devices (consumers and producers), but otherwise, the flexibility of the channels, a mechanism for backpressere, and the extra coordination efforts, namely alts, which Rich himself highlights as huge motivation, isn't often needed on browser clients, which tends to be views (not processing houses) and so already get curated and ordered information from their servers (producers). That isn't to say their aren't some browser clients that don't fit this mold, ones that might be doing sliding or dropping events, or can alternate between producers depending on which responses first, but this kind of thing typically isn't how I have seen browser clients used in the wider architecture. https://www.youtube.com/watch?v=AhxcGGeh5ho&t=1s David Nolan talks about how you can collect all your event handling logic into one place, which, to my earlier point, is nice only for sharing that pipeline somewhere else, which he (and i agree) is useful, as he says: > design UI's around events, not around the dom, or the iphone, or text ui, or brail, etc...but with this model we abstract out the sources we get re-usable components. However this isn't a reason to use it on the browser, in fact, it's suggests the opposite, that if the browser is your only target, this benefit, which must come at some cost, and so would actually be deterrent to browser only systems. Meanwhile, Stuart, https://www.youtube.com/watch?v=BcHI3U0FuoY, gives numerous examples, many on the browsers, imo, makes curious (i'm literately not sure here) use (around 32m) of alts to say put clicks or mouse moments on the same channel, only to, not process them together by sliding, dropping, or re-ordering but to immediately fan them out again via a conditional. To me, decoupling the processing here, is as , as nolan suggested, mostly useful for sharing across platforms, as without that, it's better to avoid the conditional and leave things more tightly coupled directly how their used with two distant states/streams. To me all of this is a bit like explaining why i prefer a regular bike, over an electric bike in a city, it's hard to examine because the context matters. To me, the added power isn't worth the extra weight (the bike is bulkier), the added speed isn't worth the unpredictability (sometimes they accelerate much faster then i'm used to), and the set to set them up and down is much greater. But I don't mean this to be preachy, i hope it's educational, for me, and for others. I have always been excited about core async, and always felt a bit disheartened when i stumble across it and it seems foreign and strange rather then inviting and comforting. So i'm trying to grow in my relationship a bit and i'm always thankful for the feedback and help from this community in that regard.

You seem to be making a few underlying assumptions that do not match my experience. Here are a few examples. > the flexibility of the channels, a mechanism for backpressure, and the extra coordination efforts, namely alts, which Rich himself highlights as huge motivation, isn't often needed on browser clients, which tends to be views (not processing houses) I've mentioned it before, but channels and backpressure are only half of the equation; core.async also gives you go. > Given most clojurescript applications don't make strong use of core async Is that a well-established fact? core.async is pretty much the first library I add to any frontend project, and it's included by default in shadow-cljs. I'm not claiming it's false, mind you, just that it's not evidently true to me. As a larger point, it may just be that core.async is not relevant to the kinds of clients you build. And that's absolutely fine. https://www.youtube.com/watch?v=DLa2FQQzCr4&t=913s has a nice breakdown of frontend complexity and why some technologies may seem "too complex" depending on one's experiences. These days, I personally tend to think of UI complexity as broadly either HTMX or CLJS, and, in that context, the CLJS part definitely includes core.async. If your frontend is "just a view" and the server has already prepared and sorted the data, you may not need CLJS at all. HTMX is a really nice way to build frontends for the use-cases it covers (which may be substantially broader than one would initially think).

I've mentioned it before, but channels and backpressure are only half of the equation; core.async also gives you go.
I don't follow. Are you talking about how the go block gives you asyncronousity?

I think they might mean that you're focusing on some of the important aspects of core.async's implementation but ignoring the other part, like how it lets you linearize async code in a flexible/powerful/elegant way. Channels with forced backpressure strategies are great for a lot of scenerios. But also writing code in the way that core.async go blocks allow is also pretty great independent of that.

I think maybe they're talking about being able to go-loop which you can't do with a promise. JS has async generator functions now, but ClojureScript I think doesn't support them. So if you want anything like an async generator in ClojureScript you have to use a/go

> I don't follow. Are you talking about how the go block gives you asyncronousity? Not sure what you mean with that word, but what I mean is if you conceptually want multiple threads of execution (as a program organization concept - concurrency as an architectural tool, not for performance), go (with loop, mostly) allows you to express that in your code very directly in the form of multiple top-level loops that look like loops.

@gaverhae can you give an example of using that in the browser that's been helpful to you?

That rant aside, I'm going to find some more examples, and play around a bit more, I'm just trying to frame those experiments a bit so i know how to judge them.

I'm happy to agree to disagree on this, but I don't think this is a fair criticism for why core.async should or shouldn't be in the browser. There are already hundreds of messages on the topic in the previous conversation, so I won't try to re-litigate here.

@smith.adriane Fair enough, you have done a lot. I'll revisit out conversations too, but i don't feel like we did a good job comparing core async to promises or what react has to offer. Given most clojurescript applications don't make strong use of core async, i think it's difficult to present the idea that it's necessary, and so there is extra effort in understanding how it might strategically fit some some contexts in the browser to act as an accelerator. Not that we need to promote or discourage anything of course. I'm thinking about it because it has crossed my path again, and i have always been excited by it. I welcome the encouragement on this topic though, so i quickly leafed through our discussion and you had asked me to take a look at these examples, one of which is https://github.com/swannodette/async-tests/blob/master/src/async_test/mouse/core.cljs:

(ns async-test.mouse.core
  (:require [cljs.core.async :refer [chan sliding-buffer put! alts!]]
            [clojure.string :as string]
            [async-test.utils.helpers :refer [event-chan set-html! by-id]])
  (:require-macros [cljs.core.async.macros :as m :refer [go]]
                   [cljs.core.match.macros :refer [match]]))
 
(def loc-div (by-id "location"))
(def key-div (by-id "key"))

(def mc (:chan (event-chan js/window "mousemove")))
(def kc (:chan (event-chan js/window "keyup")))
 
(defn handler [[e c]]
  (match [e]
    [{"x" x "y" y}] (set-html! loc-div (str x ", " y))
    [{"keyCode" code}] (set-html! key-div code)
    :else nil))

(go
  (while true
    (handler (alts! [mc kc]) )))
The problem with "recreating" is often that the implmentation might do more then is necessary to some extent, but my knee-jerk reaction here is similar to the crisitm i leveled at stuarts example: were using alt, which has no greater notion of timing or ordering then the javascript event loop, to grab things on two channels, then pull from both (effectively merging right?) into one channel then doing next to no processing (that needs any sort of backpressure) only to conditional through a handler target each of these events individual again. Also, by putting the alts in a true loop, were grabbing all the events, so why were we alternating them, all were used, the order was as if they arrived and were used individually, not funneled such that only the first was used. To me, the code doesn't help me understand the intent, (which admittedly is always a bit grey!), but it makes it hard to talk about what a translation would be. However, I think in react this would those would be two separate react hooks. The mouse event would look like this https://www.joshwcomeau.com/snippets/react-hooks/use-mouse-position/. The point isn't so much to say, coreasync isn't helping, it's to say: the interesting part of this example seems to not really use much of what the core async authors promoted to my eyes.

To me, it's better to focus on coupling rather than mechanism. In your react hook example, the author starts with mouse events, and then adds support for touch events. Consider how you would add support for touch events to the core.async code. What about adding a new type of character input? What about another type of pointer event? How would you test the react hook code? How would you test the core.async code? Notice how the final useMousePosition effect is misnamed (should be something like usePointerPosition ). Try to list the different ways each example introduces coupling.

☝️ 1

The React example, where they try to conditional handle touch and moves, isn't what i would recommend because that abstraction isn't that useful to the users of the code: e.g by the time i understand your useMousePosition also works for touch events i probably could have written the code. I'll think more about what your getting at though.

There's many times when core.async in the browser is overkill for sure. I think you need to think much bigger. You're building a full audio DAW in-browser app? You're building a game running inside the browser? You're building the slack app? Etc. If you think of yourself as building a browser app that competes with a desktop app, the reason for using core.async could be the same as why you'd use it on the desktop no?

But I also agree a bit with the "coupling". If you subscribe events to the browser mouse and click events. That sure works. Now try to test it? How do you simulate these events? What if you want to have a recording of mouse/move events and replay it? If you pipe the events on a channel, they're decoupled. You can test series of mouse/keyboard events and so on.

🙏 1

I don't remember all the details off the top of my head, but it was a turn-based game running mostly in browser with some coordination with a server.