Skip to content
AgileLogics
5 min read

Futures, Promises, and Delays

Prerequisites: Atoms

Three small tools for work that happens later or elsewhere. All three are read with deref / @.

future — run in another thread

(defn fetch [name ms]
  (Thread/sleep ms)
  (str name " data"))

(let [start (System/currentTimeMillis)
      users (future (fetch "users" 300))
      orders (future (fetch "orders" 300))]
  (println @users "+" @orders)
  (println "parallel:" (< (- (System/currentTimeMillis) start) 550)))

(def slow (future (Thread/sleep 5000) :done))
(println (deref slow 100 :timed-out))
(println (realized? slow))
(future-cancel slow)
(shutdown-agents)
users data + orders data
parallel: true
:timed-out
false
  • future runs its body on a thread pool and returns immediately.
  • @f blocks until the result is ready; (deref f timeout-ms default) waits at most that long.
  • Two 300 ms tasks together took about 300 ms, not 600.

promise — a value delivered later

(def answer (promise))

(future
  (Thread/sleep 100)
  (deliver answer 42))

(println "waiting...")
(println "got" @answer)
(deliver answer 99)                ; ignored: a promise is delivered once
(println @answer)
(shutdown-agents)
waiting...
got 42
42

A promise is an empty box that one thread fills with deliver and others wait on.

delay — compute once, on demand

(def config
  (delay
    (println "(loading config)")
    {:db "postgres://localhost/app"}))

(println "started")
(println (realized? config))
(println (:db @config))
(println (:db @config))
started
false
(loading config)
postgres://localhost/app
postgres://localhost/app

The body runs the first time the delay is dereferenced, and the result is cached — ideal for expensive resources that might not be needed.

pmap

(defn slow-square [n] (Thread/sleep 100) (* n n))

(let [start (System/currentTimeMillis)
      result (doall (pmap slow-square (range 8)))]
  (println result)
  (println "faster than sequential:" (< (- (System/currentTimeMillis) start) 700)))
(shutdown-agents)
(0 1 4 9 16 25 36 49)
faster than sequential: true

pmap is a parallel map — worthwhile only when each item takes a noticeable amount of work, since coordinating threads has a cost.