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.