Clojure agent — Asynchronous State with send, await, and Background Updates | Episode 27

0views
C
CelesteAI
Description
Some state changes shouldn't block the caller. Log lines, metrics, cache warmers — you queue them and get back to work. Clojure's `agent` is built for exactly that: a container for one value, updated asynchronously on a background thread. You `send` a pure function to the agent. Clojure queues it, runs it on an agent thread, and the agent's value becomes whatever your function returns. Your caller returned immediately — the whole point. When you need to know the sends have landed, `await` blocks until they have. In this episode we build a tiny logger, watch `send` decouple the caller from the work, and hammer the agent with a thousand concurrent sends. Every entry lands, in order. Student code: https://github.com/GoCelesteAI/clojure-for-beginners/tree/main/episode27 Every keystroke is shown on screen with generous pauses so you can follow along at your own pace. What You'll Learn: - `(agent initial-value)` — create an agent - `@agent` / `(deref agent)` — read the current value (non-blocking) - `(send a f & args)` — queue `(apply f current-value args)` on the agent thread pool; returns immediately - `(send-off a f & args)` — same, but for blocking/IO fns (separate unbounded thread pool) - `(await a)` — block until sends queued from this thread have completed - `(shutdown-agents)` — release the agent thread pools (required at end of -main) - atom vs ref vs agent — when to pick which Timestamps: 0:00 - Intro 0:15 - Preview: fire-and-forget state 0:51 - The logger agent + log! helper 1:10 - Start the REPL, load the namespace 1:35 - @logger — an empty vector 1:43 - send — queue conj, caller returns immediately 1:50 - @logger — the conj already ran 1:58 - log! two more entries 2:10 - await — block until sends finish 2:17 - All three entries in order 2:26 - Five more sends in a loop — no waiting 2:40 - After await, all five are there 2:51 - Control-D 3:06 - clj -M:run — 1000 async sends, all landing 3:17 - Recap 3:55 - What's next: Episode 28 Key Takeaways: 1. An agent is one value, updated asynchronously on its own thread. 2. `send` queues a fn; the caller returns *now* and the fn runs later on the agent thread. 3. Sends from the same thread always run in the order you sent them — no retries, no race conditions on this agent's value. 4. `await` is your synchronization point: block until every send queued from this thread has committed. 5. Agent fns can have side effects (unlike atom's swap! fn) — the agent runs them serially, so there's no "called twice" concern. 6. End your `-main` with `(shutdown-agents)` — otherwise the agent thread pools keep the JVM alive. Phase 5 continues. Next up: `future` and `promise` — async *computation* (vs. async *state*) with one-shot values. Taught by CelesteAI. Like and subscribe for more Clojure tutorials!
Back to tutorials

Duration

Added to Codegiz

April 20, 2026

📖 Read the articleOpen in YouTube