At first glance the cljs community doesn't seem short on libs that give await/async-like abstractions and from what I recall JS interop in CLJS was smooth enough to operate directly against Promise objects.
In Rich Hickey's famous / often-referenced "Simple Made Easy" talk he lists what he considers simple vs complex toolkits. [1]
There's a clear preference for declarative constructs vs imperative constructs. Async/await is attempting to take async behavior and make it "appear" synchronous. I suspect Hickey would slot this into the complexity category. At least I tend to agree. I think channels are both explicit about what is going on and also maximally decoupled/composable.
It seems that your preference leans the other way. I just point this^ out b/c it is possibly or likely an explicit choice and not a miss by the Clojurescript team (Nolan?).
I personally find the freedom given by Clojure/script ultimately simple and powerful.
I'm confused. Doesn't core.async do the same? The go macro applies a transformation on the body that is analogous to what happens with async/await. The difference is the async/await contract is built on top of promises which define a single future value or an error, while core.async uses a channel which can only yield non-nil values (no place for errors).
Yes, you can do more with channels. But you can also use them to implement simple "blocking" calls that return one single value (This is used quite a lot in Go, where doing basic blocking IO on a file involves underlying channels between a facade layer and IO scheduler thread).
Presenting an illusion of sequential code is precisely what core.async does. It's magic and hides complexity (which can leak back and bite you)
You're not confused. I think you're right that both cljs/go and js/async blocks are giving a 'procedural way' to basically create an asynchronous processing/state machine.
It would be interesting to see a fully-baked out analysis of async/await vs go and Promises vs channels.
I do think Clojure still has the win on simplicity if for the main reason that core.async is just a library [2] and Javascript implements async/await via syntax with the actual implementation at a subterranean level.
Clojure's version (core.async) can be macroexpanded/unwound -- i.e., the 'magic' you speak of is not magic at all and can be examined and implemented directly w/in ClojureScript itself.
From the channels vs Promise perspective I think there is more to argue. Channels are quite a simple concept when weighed against the Promise spec [3]; of course this is arguable and arguments welcome.
Also worht considering David Nolen's takes, one of them here: [4]
we're mixing up language implementation issues and the underlying primitives.
https://github.com/athos/kitchen-async offers a macro-based pure clojure expansion/unwinding of calls to functions returning promises, in a way that is similar to what javascript does with their treating of functions annotated with the async keyword that invoke other functions through the await keyword.
For me, the interesting bit is more in the difference in the primitive building block: a channel of values (where nil is disallowed) vs the result of a "computation" which can either succeed or fail.
The channel approach is the most minimal; in fact you can implement a "computation" on top of the channel approach by passing tuples of (result, error) in a channel and then closing it.
Comments
At first glance the cljs community doesn't seem short on libs that give await/async-like abstractions and from what I recall JS interop in CLJS was smooth enough to operate directly against Promise objects.
In Rich Hickey's famous / often-referenced "Simple Made Easy" talk he lists what he considers simple vs complex toolkits. [1]
There's a clear preference for declarative constructs vs imperative constructs. Async/await is attempting to take async behavior and make it "appear" synchronous. I suspect Hickey would slot this into the complexity category. At least I tend to agree. I think channels are both explicit about what is going on and also maximally decoupled/composable.
It seems that your preference leans the other way. I just point this^ out b/c it is possibly or likely an explicit choice and not a miss by the Clojurescript team (Nolan?).
I personally find the freedom given by Clojure/script ultimately simple and powerful.
Curious for others' thoughts on this.
[1] https://www.google.com/search?q=%22simplicity+toolkit%22+hic...
I'm confused. Doesn't core.async do the same? The go macro applies a transformation on the body that is analogous to what happens with async/await. The difference is the async/await contract is built on top of promises which define a single future value or an error, while core.async uses a channel which can only yield non-nil values (no place for errors).
Yes, you can do more with channels. But you can also use them to implement simple "blocking" calls that return one single value (This is used quite a lot in Go, where doing basic blocking IO on a file involves underlying channels between a facade layer and IO scheduler thread).
Presenting an illusion of sequential code is precisely what core.async does. It's magic and hides complexity (which can leak back and bite you)
You're not confused. I think you're right that both cljs/go and js/async blocks are giving a 'procedural way' to basically create an asynchronous processing/state machine.
It would be interesting to see a fully-baked out analysis of async/await vs go and Promises vs channels.
I do think Clojure still has the win on simplicity if for the main reason that core.async is just a library [2] and Javascript implements async/await via syntax with the actual implementation at a subterranean level.
Clojure's version (core.async) can be macroexpanded/unwound -- i.e., the 'magic' you speak of is not magic at all and can be examined and implemented directly w/in ClojureScript itself.
From the channels vs Promise perspective I think there is more to argue. Channels are quite a simple concept when weighed against the Promise spec [3]; of course this is arguable and arguments welcome.
Also worht considering David Nolen's takes, one of them here: [4]
[1] https://clojure.github.io/core.async/
[2] https://clojure.org/news/2013/06/28/clojure-clore-async-chan...
[3] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
[4] https://swannodette.github.io/2013/08/23/make-no-promises
Edits: typos/cosmetic.
we're mixing up language implementation issues and the underlying primitives.
https://github.com/athos/kitchen-async offers a macro-based pure clojure expansion/unwinding of calls to functions returning promises, in a way that is similar to what javascript does with their treating of functions annotated with the async keyword that invoke other functions through the await keyword.
For me, the interesting bit is more in the difference in the primitive building block: a channel of values (where nil is disallowed) vs the result of a "computation" which can either succeed or fail.
The channel approach is the most minimal; in fact you can implement a "computation" on top of the channel approach by passing tuples of (result, error) in a channel and then closing it.