Back to Blog

Clojure for Beginners: Clojure agent — Asynchronous State with send, await, and Background Updates

Celest KimCelest Kim

Video: Clojure agent — Asynchronous State with send, await, and Background Updates | Episode 27 by CelesteAI

Watch full page →

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

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.

Code

(ns app.core)

;; An agent holds a log — a vector of entries.
;; send queues a fn against it; the fn runs on an agent thread.
(def logger (agent []))

(defn log!
  "Fire-and-forget: queue an entry on the logger.
   Returns the agent immediately — the actual conj happens asynchronously."
  [entry]
  (send logger conj entry))

(defn -main
  "Run with: clj -M:run"
  [& _]
  (log! "server started")
  (log! "request /users")
  (log! "request /products")
  (log! "shutdown")

  ;; `await` blocks until all pending sends have completed.
  (await logger)

  (println "=== log ===")
  (doseq [e @logger] (println " ·" e))

  ;; Reset the log and hammer it with 1000 async sends.
  (send logger (fn [_] []))
  (await logger)

  (dotimes [i 1000]
    (log! (str "entry-" i)))
  (await logger)

  (println)
  (println "After 1000 async log!s, count:" (count @logger))

  ;; Essential — otherwise the agent thread pool keeps the JVM alive.
  (shutdown-agents))

Key Points

Watch the video above for a full walkthrough — every keystroke is shown so you can code along.

Student code: GitHub