Created
May 24, 2025 20:53
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; 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