async function baz() {
await* [foo(), bar()];
}
(defn baz []
(go
(doseq [c [(foo) (bar)]]
(<! c))))
With the core.async case you have to define the channel c, right?
It looks cryptic compared to the es7 version. Like "go" what does go mean, seriously? I mean in terms of its English language context. Go does not convey async. And what the heck is <! Are we using bash or something? Some kind of inverted redirection?
I guess you can have macros that would make it look just as comprehensible as the es7 async version so people coming into CLJS won't be turned off by the crazy looking syntax and the exposed low level semantics. Maybe a bunch of core.async macros that expose common use cases in a way that anyone can understand without even having to understand CSP basics.
In my team, everyone gets the es7 version of things but despite having been CLJS users for 6 months now, no one understands how to use core.async. I've had to play with it in different languages before I realized how powerful it is to have in your toolset to manage complex (potentially dynamic) coordination patterns between async processes but our use cases in the UI have yet to beyond the very simple use cases your gist shows which are (without use of macros) much easier to understand using es7 async functions.
If macros can solve the "comprehensibility" problem for the common use cases then maybe something that would provide es7 async like library for cljs that gives you defnasync and await
Syntax and semantics can then be so simple while the underlying system remains so powerful and in that case you could have core.async be bundled with those macros thus allowing easy access to common async patterns without the Go syntax obfuscating things and making it seem complicated as well as too noisy syntax wise for the most common tasks
You can emulate C# async/await by thinking of channels as tasks (every function that returns a task in C# is awaitable).
(defn async-get [url]
(let [c (channel)]
(ajax/get url {:when-done #(>!! c %)}) ;; >!! puts something into a channel outside of a go-block
c))
(go
(<! (async-get "some/url"))) ;; <! receives a value inside of a go-block, just as await does inside an async function
The code above would be the exact same in C#, except in C# you have async functions in the standard library, so you rarely need to make your own "async-get" function. Channels do become more intereseting in combination with alt and timeout.
(go (alt!
(async-get "some/url") ([val] val)
(timeout 1000) ([_] "failed")))
The above code will return "failed" unless our async-get call completes within a second, if that happens it will return the result of async-get. We can have many more channels within our alt! call, and only one value is exposed from one channel.
In the end, core.async is about making async code easy to reason about. Use it as async/await if you wish, or use it like in Go.
;; OPTION 3: using Promises | |
;; If you're in ClojureScript, and not interested in core.async, you can just use a Promise library: | |
;; - funcool/promesa is a ClojureScript wrapper of the popular Bluebird JavaScript library. | |
;; - jamesmacaulay/cljs-promises is a Promise library designed to operate nicely with core.async. | |
;; Promises take care of both asynchrony and error management (they're essentially a mix of Futures and Exception Monads); some may say it's convenient, others may argue it's not simple. | |
While he has a good point to make about the given scenarios, his complaining about loss of generality with async/await compared to generators and promise api on which async/await is built is misplaced. Loss of generality is the obvious consequence of trying to hit the most common use cases with the simplest possible syntax and semantics... i.e. you end up losing true generality.The argument I wanted to validate here is if you can implement the very common and simple async coordination scenarios with async/await much more clearly and suitably than with CSP and if CSP itself as a model can be implemented/built using async/await (for more complicated coordination scenarios) then it seems to me that async/await would make a more universal basis for concurrency management than starting with CSP.
I knew about cats from this mailing list, and I went over examples showing the use of mlet but did not come across any examples of alet, so thank you for the whole explanation. Are the awaits try-catchable (assuming the function being called can throw)?
Innovations start in languages like Java, Clojure, Haskel, C# and Elm et al and make their way into JS eventually but what I was suggesting is "core.async" should probably be expanded beyond CSP alone as as many new comers to CLJ/S, such as myself and a few other relative beginners, often think that core.async is the basis for all async stuff you can do in CLJ/S, which as you are explaining is not true, and it takes a while to discover libraries such as cats.
Will be definitely playing with the cats patterns you presented here and going over it in more detail.Also, have you seen this? https://github.com/dvlsg/async-csp ... It is CSP style channels implemented with ES7 async/await ...
thank you for this!