Skip to content

Instantly share code, notes, and snippets.

@ertugrulcetin
Created May 24, 2025 20:53
Show Gist options
  • Save ertugrulcetin/fbde8e5ff2c2077f51c2da3a7806f969 to your computer and use it in GitHub Desktop.
Save ertugrulcetin/fbde8e5ff2c2077f51c2da3a7806f969 to your computer and use it in GitHub Desktop.
ClojureScript macro alet that enables async/await-like syntax for JavaScript Promises and core.async channels. It supports sequential binding with optional (await ...) expressions and body steps, and includes an optional (catch err ...) clause for error handling.
;; ClojureScript macro alet that enables async/await-like syntax for JavaScript Promises and core.async channels.
;; It supports sequential binding with optional (await ...) expressions and body steps, and includes an optional (catch err ...) clause for error handling.
(defmacro alet [bindings & body]
(let [last-expr (last body)
[body catch]
(if (and (seq? last-expr) (= 'catch (first last-expr)))
[(butlast body) last-expr]
[body nil])
;; Parse bindings into pairs like let does
binding-pairs (partition 2 bindings)]
(letfn [;; Check if expression is (await ...)
(await-form? [expr]
(and (seq? expr) (= 'await (first expr))))
;; Extract inner expression from (await expr)
(await-expr [expr]
(second expr))
;; Convert promise-chan to JS Promise
(promise-chan->js-promise [val-sym]
`(js/Promise.
(fn [resolve# reject#]
(async/take! ~val-sym
(fn [result#]
(if (instance? js/Error result#)
(reject# result#)
(resolve# result#)))))))
;; Await a value (JS Promise or promise-chan)
(await-value [val-sym continue-fn]
`(cond
;; JavaScript Promise
(and ~val-sym (.-then ~val-sym))
(-> ~val-sym (.then ~continue-fn))
;; ClojureScript promise channel
(and ~val-sym (satisfies? cljs.core.async.impl.protocols/ReadPort ~val-sym))
(-> ~(promise-chan->js-promise val-sym) (.then ~continue-fn))
;; Not a promise, call continue immediately
:else (~continue-fn ~val-sym)))
;; Process body expressions sequentially
(process-body [exprs]
(if (empty? exprs)
nil
(let [expr (first exprs)
rest-exprs (rest exprs)]
(if (await-form? expr)
;; It's an (await ...) form - await it
(let [inner-expr (await-expr expr)
result-sym (gensym "result")]
`(let [~result-sym ~inner-expr]
~(await-value result-sym
(if (empty? rest-exprs)
`(fn [val#] val#) ; Last expression, return value
`(fn [~'_] ~(process-body rest-exprs)))))) ; Continue with rest
;; Regular expression - execute and continue
(if (empty? rest-exprs)
expr ; Last expression, return its value
`(do ~expr ~(process-body rest-exprs))))))) ; Execute and continue
;; Build nested promise chain for bindings
(build-chain [pairs]
(if (empty? pairs)
;; No more pairs, process the body
(if (empty? body)
nil
(process-body body))
;; Process next binding
(let [[name thenable] (first pairs)
next-chain (build-chain (rest pairs))]
(if (await-form? thenable)
;; It's an (await ...) form - await it
(let [inner-expr (await-expr thenable)
val-sym (gensym "val")]
`(let [~val-sym ~inner-expr]
~(await-value val-sym `(fn [~name] ~next-chain))))
;; Regular expression - bind directly
`(let [~name ~thenable]
~next-chain)))))]
(let [chain (build-chain binding-pairs)]
;; Apply catch clause to the entire chain if present
(if catch
(let [[_ catch-name & catch-body] catch]
`(-> ~chain
(.catch (fn [~catch-name] ~@catch-body))))
chain)))))
(defn init []
(alet [a (await (some-promise))
b (await (some-promise))]
(println "Result: " (+ a b))
(println "Hey")
(await (some-promise))
(println "Bye")))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment