Skip to content

Instantly share code, notes, and snippets.

@vitalipe
Last active June 28, 2023 20:27
Show Gist options
  • Save vitalipe/ac2ab737115fed76dad8bc933c5dc9ca to your computer and use it in GitHub Desktop.
Save vitalipe/ac2ab737115fed76dad8bc933c5dc9ca to your computer and use it in GitHub Desktop.
async/await cljs macros
(ns alpakit.async
(:require [cljs.core.async]
[cljs.core.async.interop :refer-macros [<p!]]))
(defn promise? [obj]
#?(:clj false)
#?(:cljs (= js/Promise (type obj))))
(defn all [& promises]
#?(:clj nil)
#?(:cljs (.all js/Promise (apply array promises))))
(defmacro async
"Similar to async/await in JS, returns js/Promise.
(async
(let [x (await! (js/Promise.resolve 42))
y (await! 42) ;; <- note: simple values are fine
z 42]
(await! (side-effect! ...)) ;;
(= x y z 42))) ;; will return true
"
[& code]
`(js/Promise.
(fn [resolve!# reject!#]
(cljs.core.async/take!
(cljs.core.async/go
(try {:status :ok :val (do ~@code)}
(catch :default error# {:status :error :val error#})))
;; take! callback
(fn [{status# :status val# :val}]
(case status#
:ok (resolve!# val#)
:error (reject!# val#)))))))
(defmacro await!
"Must run inside (async ...) block.
`expr` Can be js/Promise or a value, returns js/Promise
"
[expr]
`(cljs.core.async.interop/<p!
(let [p# ~expr]
(if (promise? p#)
p#
(js/Promise.resolve p#)))))
(defmacro let-await
"Shorter version of async+let, with error handiling.
First top-level catch form inside body will be used to handle promise rejection.
Returns js/Promise.
(let-await [a (js/Promise.resolve 1)
b (js/Promise.resolve 2)])
Same as:
(async
(let [a (await! (js/Promise.resolve 1)
b (await! (js/Promise.resolve 2)])
With catch:
(let-await [page (fetch \"https://clojure.org/\")
text (.text page)]
(catch :default e
(println \"Error!\" e))
(println text))
"
[bindings & body]
(let [keep-when-index (fn [f coll] (keep-indexed #(when (f %1) %2) coll))
catch-form? (fn [form] (and (seq? form) (= 'catch (first form))))
catch-form (first (filter catch-form? body))
body (remove (partial = catch-form) body)
bindings (interleave
(->> bindings
(keep-when-index even?))
(->> bindings
(keep-when-index odd?)
(map (fn [code] `(await! ~code)))))]
(if-not (empty? catch-form)
`(.catch
(async (let [~@bindings] ~@body))
(fn [err#]
(try
(throw err#)
~catch-form)))
`(async
(let [~@bindings] ~@body)))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment