Recently I had to write some code using Promises and thought it was time for some syntax sugar. So I added a very simplistic macro to basically emulate what JS await
does while actually mapping to traditional Promise
.then
and .catch
. This way the macro can stay extremely simple without having to modify the compiler to add support for async functions and actual await
. That is a quest for another day.
Currently this is only available as part of shadow-cljs
(since 2.19.1
) but the macro you can easily copy to a separate namespace and use anywhere.
The use is very simple. You get it by requiring [shadow.cljs.modern :refer (js-await)]
in your ns
or REPL.
(defn my-async-fn [foo]
(js-await [the-result (promise-producing-call foo)]
(doing-something-with-the-result the-result)
(catch failure
(prn [:oh-oh failure])))
Without the js-await
this was (and is what the macro will emit)
(defn my-async-fn [foo]
(-> (promise-producing-call foo)
(.then (fn [the-result]
(do-something-with-the-result the-result)))
(.catch (fn [failure]
(prn [:oh-oh failure])))))
Also sort of fine but gets a little verbose.
Both map pretty much to this JS
function my_async_fn(foo) {
promise_producing_call(foo)
.then(function(the_result) {
return doing_something_with_the_result(the_result);
})
.catch(function(failure) {
console.log("oh-oh", failure)
})
}
which more or less would map to actual async/await.
async function my_async_fn(foo) {
try {
let the_result = await promise_producing_call(foo);
return doing_something_with_the_result(the_result);
} catch (failure) {
console.log("oh-oh", failure);
}
}
catch
is optional, but same as try
if it is the last form of the body it will be used for the .catch
or a promise.
(defn my-async-fn [foo]
(js-await [the-result (promise-producing-call foo)]
(doing-something-with-the-result the-result))
Await can be nested as long as it is the last form in the body the promise chaining will just work.
(defn my-fetch-fn []
(js-await [res (js/fetch "/some.json")]
(js/console.log "res" res)
(js-await [body (.json res)]
(js/console.log "got some json" body))))
(js/console.log "my-fetch-fn" (my-fetch-fn))
The syntax is just
(js-await [binding promise-thing-or-call]
... body)
Note that js-await
always returns a promise. There is currently no error checking of any kind so things will break if the promise-thing-or-call
doesn’t actually return a promise (or strictly speaking a “thenable”). binding
can use destructuring so {:keys [foo] :as x}
is valid. Only a single binding-pair is valid, everything else will be discarded. There are also no checks for that so beware.
Thats it really. I just wanted some extremely basic syntax sugar since nesting some .then
calls got a little annoying otherwise. I’m aware of alternative solutions but they do too much for my taste.
Maybe this is useful for someone else.