I was thinking about whether or not creating async/await for cljs would be a useful project to learn about CPS transforms and along the way I discovered that core.async already does everything async/await does and a whole lot more. The syntax is very different and I think that’s what stopped me from seeing the relation sooner.
In javascript, async/await is based around promises. An async function always returns a promise and any promise can be awaited inside an async function.
In core.async, one uses channels instead. But with a pair of macros we can create the same syntax as javascript:
(defmacro async [& body]
`(let [out# (chan)]
(go
(if-let [res# (do ~@body)]
(>! out# res#)
(close! out#)))
out#))
(defmacro await [bindings & body]
{:pre [(even? (count bindings))]}
`(let [~@(mapcat (fn [[bind# val#]]
(list bind# (list '<! val#)))
(partition 2 bindings))]
~@body))
The async
macro takes a body, wraps it in a go block and returns a channel which will eventually yield the return value of the body (or close if body returns nil).
await
works just like let
, except that the values on the right hand side are channels. It would work like this:
(defn slow-inc [x]
(async
(<! (timeout 1000))
(inc x)))
(defn multi-inc [x y]
(async
(await [i (slow-inc x)
j (slow-inc y)
k (async (* i j))]
(println [i j k])
[i j k])))
(multi-inc 4 5)
; => returns a channel
; => prints [5 6 30] 2 seconds later
You can see that the syntax is getting closer to javascript. In addition, you could implement promises using core.async (there already is a promise-chan) and use those instead of channels. In this case that might be more clear.
If you look at the await
macro again, you’ll see that it waits for the async values one at a time. We could rewrite it to grab them in parallel as well. If we wanted to be really fancy, we could topologically sort the bindings and parallelise what we can while still allowing dependencies between bindings.
So in answer to your question, no there’s no ready to go solution to async/await in clojure(script) (not that I’m aware of in any case), but core.async give you the building blocks to build whatever solution you need.