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, computeasync-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 / finallyAll 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 builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |