async / await for Clojure.
A Clojure library that brings JavaScript's intuitive async/await and Promise APIs to Clojure, along with utilities for cancellation, timeouts, racing, and more. It brings familiar async/await style of programming to Clojure.
(ns example.core
(:require [com.xadecimal.async-style :as a
:refer [async blocking compute await wait cancel! cancelled?]]))
;; Simple async operation
(async
(println "Sum:" (await (async (+ 1 2 3)))))
;; Blocking I/O, asynchronous waiting
(async
(println (await (blocking
(Thread/sleep 500)
"Blocking completed!"))))
;; Blocking I/O, synchronous waiting
(println "Sync blocking result:"
(wait (blocking
(Thread/sleep 500)
"Blocking sync done")))
;; Heavy compute, asynchronous waiting
(async
(println "Factorial:"
(await (compute (reduce * (range 1 20))))))
;; Heavy compute, synchronous waiting
(println "Sync compute factorial:"
(wait (compute (reduce * (range 1 20)))))
;; Handle an error
(async
(println "Divide:" (await (async (/ 1 0))))
(catch ArithmeticException _
(println "Can't divide by zero!")))
;; Cancel an operation
(let [task (blocking (Thread/sleep 5000)
(when-not (cancelled?)
(println "This won't print")))]
(Thread/sleep 1000)
(cancel! task "Cancelled!")
(println "Task result:" (wait task)))
[com.xadecimal/async-style "0.1.0"]
{:deps {com.xadecimal/async-style {:mvn/version "0.1.0"}}}
Core.async and the CSP style is powerful, but its go
blocks and channels can feel low‑level compared to JS Promises and async/await. async-style provides:
async
/await
like in JavaScript, Python, C#, etc.blocking
/wait
for I/O and compute
/wait
for heavy compute.async
), blocking I/O (blocking
) and CPU‑bound work (compute
).promise-chan
.then
, chain
, race
, all
, any
, all-settled
.ado
, alet
, clet
for sequential, ordered, or concurrent binding.async
/await*
, if preferred.async
, blocking
, compute
async-style follows the best practice outlined here: Best practice for async/blocking/compute pools
Meaning it offers async
/blocking
/compute
, each are meant to specialize the work you will be doing so they get executed on a pool that is most optimal.
Macro | Executor pool | Use for… |
---|---|---|
async | core.async’s go‑dispatch (8 threads) | async control flow |
blocking | unbounded cached threads | blocking I/O, sleeps |
compute | fixed agent pool (cores + 2 threads) | CPU‑intensive work, do not block |
Macro | Executor pool | Use for… |
---|---|---|
async | unbounded cached threads | async control flow |
blocking | unbounded cached threads | blocking I/O, sleeps |
compute | fixed agent pool (cores + 2 threads) | CPU‑intensive work, do not block |
Macro | Executor pool | Use for… |
---|---|---|
async | virtual thread executor | async control flow |
blocking | virtual thread executor | blocking I/O, sleeps |
compute | fixed agent pool (cores + 2 threads) | CPU‑intensive work, do not block |
await
, wait
, await*
, wait*
await
(inside async
): parks current go‑thread until a promise‑chan completes; re‑throws errors.wait
(outside async
): blocks calling thread for a result or throws error.await*
/ wait*
: return exceptions as values (no throw).await
is like your JavaScript await, but just like core.async's go
, it cannot park across function boundaries,
so you have to be careful when you use macros like map
or run!
, since those use higher-order functions,
you cannot await
with them. This is true in JavaScript's await as well, but it's less common to use
higher-order functions in JS, so it doesn't feel as restrictive. Once core.async support for virtual thread is added, and if you run under a JDK that supports them, this limitation will go away.
wait
is the counter-part to await
, it is like await
but synchronous. It doesn't color your functions, but will block your thread.
await*
/ wait*
are variants that return the exception as a value, instead of throwing.
try
/ catch
/ finally
All of async
, blocking
, compute
and await
:
try
if you include any trailing
(catch …)
or (finally …)
forms.(catch Type e …)
or (finally …)
at the end of your block
into that try
, so you don’t have to write it yourself.;; without implicit try, you’d need:
(async
(try
(/ 1 0)
(catch ArithmeticException e
(println "oops:" e))
(finally
(println "done"))))
;; with implicit try, just append catch/finally:
(async
(/ 1 0)
(catch ArithmeticException e
(println "oops:" e))
(finally
(println "done")))
This gives you JS‑style inline error handling right in your async blocks.
async-style gives you first‑class combinators to catch, recover, inspect or always run cleanup on errors:
catch
Intercept errors of a given type or predicate, return a fallback value:
;; recover ArithmeticException to 0
(-> (async (/ 1 0))
(catch ArithmeticException (fn [_] 0)))
finally
Always run a side‑effect (cleanup, logging) on both success or error, then re‑deliver the original result or exception:
(-> (async (/ 1 0))
(finally (fn [v] (println "Completed with" v))))
handle
Always invoke a single handler on the outcome (error or value) and deliver its return:
;; log & wrap both success and error
(-> (async (/ 1 0))
(handle (fn [v] (str "Result:" v))))
then
Attach a success callback that is skipped if an error occurred (short‑circuits on error):
(-> (async 5)
(then #(+ % 3))
(then println))
chain
Shorthand for threading multiple then
calls, with the same short‑circuit behavior:
(-> (async 5)
(chain inc #(* 2 %) dec)
(handle println))
await*
/ wait*
By default await
/wait
will throw any exception taken from a chan. If you prefer railway programming (errors as values), use:
await*
(parking, non‑throwing)wait*
(blocking, non‑throwing)They return either the successful value or the Throwable
. Functions error?
and ok?
can than be used to branch on their result:
(async
(let [result (await* (async (/ 1 0)))]
(if (error? result)
(println "Handled error:" (ex-message result))
(println "Success:" result))))
Errors are always modeled this way in async-style, unlike in JS which wraps errors in a map, in async-style they are either an error? or an ok? result:
(async
(let [vals (await* (all-settled [(async 1) (async (/ 1 0)) (async 3)]))]
(println
(map (fn [v] (if (error? v) :err v)) vals))))
;; ⇒ (1 :err 3)
Cancellation in async-style is cooperative—you signal it with cancel!
, but your code must check for it to actually stop.
cancel!
(cancel! ch) ; cancel with CancellationException
(cancel! ch custom-val) ; cancel with custom-val (must not be nil)
Marks the promise‑chan ch
as cancelled. If the block hasn’t started, it immediately fulfills; otherwise it waits for you to check.
cancelled?
(when-not (cancelled?) …)
Returns true
inside async
/blocking
/compute
if cancel!
was called. Does not throw.
check-cancelled!
(check-cancelled!) ; throws InterruptedException if cancelled
Throws immediately when cancelled, so you can use it at safe points to short‑circuit heavy loops or I/O.
Handling cancellation downstream
await
/ wait
will re‑throw a CancellationException
(or return your custom val).(def work
(async
(loop [i 0]
(check-cancelled!)
(println "step" i)
(recur (inc i)))))
;; let it run a bit…
(Thread/sleep 50)
(cancel! work)
;; downstream:
(async
(await work)
(catch CancellationException _
(println "Work was cancelled!")))
Function / Macro | Description |
---|---|
async | Run code on the async‑pool |
blocking | Run code on blocking‑pool |
compute | Run code on compute‑pool |
sleep | async sleep for ms |
defer | delay execution by ms then fulfill value or fn |
Function | Description |
---|---|
await / wait | Retrieve result (throws on error) |
await* / wait* | Retrieve result or exception as a value (railway style) |
then | Run callback on success (skips on error) |
chain | Thread multiple then calls (short‑circuits on first error) |
catch | Recover from errors of a given type or predicate |
finally | Always run side‑effect on success or error, then re‑deliver |
handle | Run handler on outcome (error or success) and deliver its return |
error? / ok? | Predicates to distinguish exception values from normal results |
Function | Description |
---|---|
sleep | Asynchronously pause for ms milliseconds; returns a promise‑chan that fulfills with nil after the delay. |
defer | Wait ms milliseconds, then asynchronously deliver a given value or call a provided fn; returns a promise‑chan. |
timeout | Wrap a channel with a ms deadline—if it doesn’t fulfill in time, cancels the original and delivers a TimeoutException (or your custom fallback). |
Function | Description |
---|---|
race | First chan (or error) to complete “wins”; cancels all others |
any | First successful chan; ignores errors; errors aggregated if all fail |
all | Wait for all; short‑circuits on first error; returns vector of results |
all-settled | Wait for all, return vector of all results or errors |
ado
, alet
, clet
, time
Macro | Description |
---|---|
ado | Asynchronous do : execute expressions one after another, awaiting each; returns a promise‑chan of the last expression. |
alet | Asynchronous let : like let but each binding is awaited in order before evaluating the body. |
clet | Concurrent let : evaluates all bindings in parallel, auto‑awaiting any dependencies between them. |
time | Measure wall‑clock time of a sync or async expression; prints the elapsed ms and returns the expression’s value. |
let
(alet
) and Concurrent let
(clet
)Bind async expressions just like a normal let
—but choose between sequential or parallel evaluation:
Macro | Semantics |
---|---|
alet | Sequential: awaits each binding in order before evaluating the next. |
clet | Concurrent: starts all bindings in parallel, auto‑awaiting any that depend on previous ones while letting independent bindings overlap. |
alet
example(alet
[a (defer 100 1)
b (defer 100 2)
c (defer 100 3)]
(println "Sum =" (+ a b c)))
;; Prints "Sum = 6" after ~300 ms
clet
example(clet
[a (defer 100 1)
b (defer a 2) ;; waits for `a`
c (defer 100 3)] ;; independent of `a` and `b`
(println "Sum =" (+ a b c)))
;; Prints "Sum = 6" after ~200 ms (a and c run in parallel; b waits on a)
alet
when bindings must run in sequence.clet
to maximize concurrency, with dependencies automatically respected.Contributions, issues, and feature requests are welcome! Feel free to submit a pull request or open an issue on GitHub.
Distributed under the MIT License. See LICENSE for more details.
If you find async-style
useful, star ⭐️ the project and share it with the community!
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close