Last active
June 28, 2023 20:27
-
-
Save vitalipe/ac2ab737115fed76dad8bc933c5dc9ca to your computer and use it in GitHub Desktop.
async/await cljs macros
This file contains 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
(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