Skip to content

Instantly share code, notes, and snippets.

@latant
Last active April 23, 2026 15:02
Show Gist options
  • Select an option

  • Save latant/57eb02ccad29e4e0d626f87d6665fa11 to your computer and use it in GitHub Desktop.

Select an option

Save latant/57eb02ccad29e4e0d626f87d6665fa11 to your computer and use it in GitHub Desktop.
ClojureScript async/await macros via cloroutine
;; async/await macros for ClojureScript via cloroutine
;; Requires: cloroutine "13" (shadow-cljs.edn :dependencies)
;;
;; Usage:
;; (ns my.ns
;; (:require [async-await])
;; (:require-macros [async-await :refer [async await]]))
;;
;; (async
;; (let [data (await (fetch-something))]
;; (println data)))
;;
;; NOTE: Use (.method js/Obj args) instead of (js/Obj.method args) inside
;; async blocks. See https://github.com/leonoel/cloroutine/issues/25
(ns async-await
#?(:cljs (:require [cloroutine.impl]))
#?(:cljs (:require-macros [cloroutine.core :refer [cr]]
[async-await])))
;; --- ClojureScript runtime ---
#?(:cljs
(do
(def ^:dynamic *fiber* nil)
(def ^:dynamic *value* nil)
(def ^:dynamic *error* nil)
(defn -await [p]
(let [fiber *fiber*]
(.then p
(fn [v] (fiber v nil))
(fn [e] (fiber nil e)))))
(defn thunk []
(if-some [e *error*] (throw e) *value*))))
;; --- Macros (runs on JVM / Clojure side) ---
(defmacro await
"Suspends the current async coroutine until the JS Promise p resolves.
Returns the resolved value, or throws on rejection.
Must be used inside an async block."
[p]
`(-await ~p))
(defmacro async
"Wraps body in a JS Promise backed by a cloroutine coroutine.
Inside the body you can use (await <promise>) to park without blocking.
WARNING: Due to a known cloroutine bug (https://github.com/leonoel/cloroutine/issues/25),
avoid (js/Foo.method args...) call syntax inside async blocks. Use (.method js/Foo args...)
instead. This includes indirect call positions such as threading macros:
Bad: (await (js/Promise.resolve x))
Good: (await (.resolve js/Promise x))"
[& body]
`(js/Promise.
(fn [resolve# reject#]
(let [fiber-ref# (volatile! nil)
cr# (cloroutine.core/cr {async-await/-await async-await/thunk}
(try (resolve# (do ~@body))
(catch :default e#
(reject# e#))))]
(vreset! fiber-ref#
(fn [v# e#]
(binding [async-await/*fiber* @fiber-ref#
async-await/*value* v#
async-await/*error* e#]
(cr#))))
(binding [async-await/*fiber* @fiber-ref#]
(cr#))))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment