1 Introduction
1.1 Overview
std.lib.future is part of the standard foundation library set. This page collects the public API reference for the namespace.
2 Walkthrough
2.1 Creating futures
std.lib.future wraps CompletableFuture with a Clojure-friendly API. future:call runs a function asynchronously, completed creates an already-done future, and incomplete creates a promise-like future.
create and complete futures
^{:refer std.lib.future/completed :added "3.0"}
@(completed 1)
=> 1
^{:refer std.lib.future/future:call :added "3.0"}
@(f/future:call (fn [] 1))
=> 1
^{:refer std.lib.future/incomplete :added "3.0"}
(f/future:incomplete? (incomplete))
=> true
2.2 Timeouts and values
future:timeout adds a timeout with an optional default. future:value blocks for the result, and future:now returns a default if not yet complete.
control future completion
^{:refer std.lib.future/future:timeout :added "3.0"}
@(-> (f/future:call (fn [] (Thread/sleep 100)))
(f/future:timeout 10 :ok))
=> :ok
^{:refer std.lib.future/future:value :added "3.0"}
(-> (f/future:run (fn [] 1))
(f/future:value))
=> 1
^{:refer std.lib.future/future:now :added "3.0"}
(-> (f/future:run (fn [] (Thread/sleep 100)))
(f/future:now :invalid))
=> :invalid
2.3 Exception handling
Query and force completion states with future:exception, future:complete?, and future:force.
handle exceptional completion
^{:refer std.lib.future/future:exception :added "3.0"}
(-> (f/future:run (fn [] (throw (ex-info "ERROR" {}))))
(f/future:exception))
=> Throwable
^{:refer std.lib.future/future:complete? :added "3.0"}
(f/future:complete? (completed 1))
=> true
3 API
- *pools*
- catch
- completed
- exception-fn
- failed
- fulfil
- future
- future:call
- future:cancel
- future:cancelled?
- future:chain
- future:complete?
- future:dependents
- future:done
- future:exception
- future:exception?
- future:fn
- future:force
- future:incomplete?
- future:lift
- future:nil
- future:now
- future:obtrude
- future:result
- future:run
- future:status
- future:success?
- future:timeout
- future:timeout?
- future:value
- future:wait
- future?
- incomplete
- on:all
- on:any
- on:cancel
- on:complete
- on:exception
- on:success
- on:timeout
- then
*pools* ^
NONE
(defonce ^:dynamic *pools*
(delay {:default clojure.lang.Agent/soloExecutor
:pooled clojure.lang.Agent/pooledExecutor
:async (ForkJoinPool/commonPool)}))
link
v 3.0
(defmacro catch
([future bindings & body]
(let [len (count bindings)]
(case len
0 `(on:exception ~future (fn [~'_] ~@body))
1 `(on:exception ~future (fn [~@bindings] ~@body))))))
link
@(-> (f/future (+ 2 3)) (f/catch [e] e)) => 5 @(-> (f/future (throw (ex-info "" {:a 1}))) (f/catch [e] (ex-data e))) => {:a 1}
v 3.0
(defn failed
([e]
(CompletableFuture/failedFuture e)))
link
@(failed (ex-info "" {})) => (throws)
fulfil ^
[future f] [future f print] [future f print skip-success]
fulfils the return with a function
v 3.0
(defn fulfil
([future f]
(fulfil future f false))
([future f print]
(fulfil future f false false))
([future f print skip-success]
(let [[status data exception] (try
[:success (f) nil]
(catch Throwable t
(if print (.printStackTrace t))
[:error nil t]))]
(if-not (and (= status :success)
skip-success)
(if (= status :success)
(future:force future data)
(future:force future :exception exception))
data))))
link
(fulfil (incomplete) (fn [] (+ 1 2 3))) => future? (fulfil (incomplete) (fn [] (throw (ex-info "Hello" {})))) => future?
v 3.0
(defmacro future
([]
`(future:run (bound-fn []) {}))
([opts? & body]
(let [[opts body] (cond (map? opts?) [opts? body]
:else [{} (cons opts? body)])]
`(future:run (bound-fn [] ~@body)
~opts))))
link
@(f/future (Thread/sleep 100) 1) => 1
future:call ^
[obj] [obj {:keys [pool delay], :as m}]
can create a future from a function or :init/future
v 3.0
(defn ^CompletableFuture future:call
([obj]
(future:call obj {}))
([obj {:keys [pool delay] :as m}]
(let [[f m] (cond (fn? obj)
[obj m]
(instance? CompletableFuture obj)
(let [value (.get ^CompletableFuture obj)]
(if (and (map? value)
(:future/init (meta value)))
[(:fn value) (merge value m)]))
:else
(throw (ex-info "Not valid" {:data {:input obj
:args m}})))
{:keys [pool delay]
:or {pool :default}} m
executor (if (keyword? pool)
(get @*pools* pool)
pool)]
(cond (nil? delay)
(CompletableFuture/supplyAsync (fn/fn:supplier [] (f)) executor)
:else
(let [executor (CompletableFuture/delayedExecutor delay
TimeUnit/MILLISECONDS
executor)]
(CompletableFuture/supplyAsync (fn/fn:supplier [] (f)) executor))))))
link
@(f/future:call (fn [] 1)) => 1 @(-> (f/future:fn (fn [] 1)) (f/future:call)) => 1
v 3.0
(defn ^CompletableFuture future:cancel
([^CompletableFuture future]
(doto future (.cancel true))))
link
@(-> (f/future:run (fn [] (Thread/sleep 100))) (f/future:cancel)) => (throws)
v 3.0
(defn future:cancelled?
([^CompletableFuture future]
(future:done future (.isCancelled future))))
link
(-> (f/future:run (fn [] (Thread/sleep 100))) (f/future:cancel) (f/future:cancelled?)) => true
v 3.0
(defn future:chain
([future chain]
(reduce (fn [out f]
(on:complete out
(fn [data error]
(if error
(throw error)
(f data)))))
future
chain))
([future chain marr]
(reduce (fn [out [f m]]
(on:complete out
(fn [data error]
(if error
(throw error)
(f data)))
m))
future
(map vector chain marr))))
link
@(f/future:chain (completed 1) [inc inc inc]) => 4 @(f/future:chain (failed (ex-info "ERROR" {})) [inc inc inc]) => (throws)
v 3.0
(defn future:complete?
([^CompletableFuture future]
(.isDone future)))
link
(-> (f/future:run (fn [] (throw (ex-info "Error" {})))) (f/future:wait) (f/future:success?)) => false (f/future:complete? (completed 1)) => true
v 3.0
(defn future:dependents
([^CompletableFuture future]
(.getNumberOfDependents future)))
link
(-> (doto (f/future:run (fn [] (Thread/sleep 1000))) (on:complete (fn [e] e)) (on:complete (fn [e] e))) (f/future:dependents)) => 2
v 3.0
(defmacro future:done
([future & body]
`(if-not (.isDone ~(with-meta future {:class CompletableFuture}))
(throw (ex-info "Future not complete" {:future ~future}))
(do ~@body))))
link
(f/future:done (completed 1) true) => true
v 3.0
(defn future:exception
([^CompletableFuture future]
(try
(.get future) nil
(catch Throwable t t))))
link
(-> (f/future:run (fn [] (throw (ex-info "ERROR" {})))) (f/future:exception)) => Throwable
v 3.0
(defn future:exception?
([^CompletableFuture future]
(future:done future (.isCompletedExceptionally future))))
link
(-> (f/future:run (fn [] (throw (ex-info "Error" {})))) (f/future:wait) (f/future:exception?)) => true (-> (f/future:run (fn [] (Thread/sleep 1000))) (f/future:exception?)) => (throws)
future:fn ^
[f] [f {:keys [pool timeout delay default], :as m}]
creates a future with :init/future props
v 3.0
(defn ^CompletableFuture future:fn
([f]
(future:fn f {}))
([f {:keys [pool timeout delay default] :as m}]
(doto (CompletableFuture.)
(.complete (with-meta (merge {:fn f} m)
{:future/init true})))))
link
(.get (f/future:fn (fn [] 1))) => (contains {:fn fn?})
future:force ^
[future object] [future type object]
forces a value or exception as completed future
v 3.0
(defn future:force
([^CompletableFuture future object]
(future:force future :value object))
([^CompletableFuture future type object]
(case type
:value (doto future (.complete object))
:exception (doto future (.completeExceptionally object)))))
link
(-> (f/future:run (fn [] (Thread/sleep 1000))) (f/future:force 10) (f/future:value)) => 10 (-> (f/future:run (fn [] (Thread/sleep 1000))) (f/future:force 10) (f/future:complete?)) => true (-> (f/future:run (fn [] (Thread/sleep 1000))) (f/future:force :exception (ex-info "Error" {})) (f/future:exception?)) => true
v 3.0
(defn future:incomplete?
([^CompletableFuture future]
(not (.isDone future))))
link
(-> (incomplete) (f/future:incomplete?)) => true
v 3.0
(defn future:lift
([obj]
(cond (nil? obj)
(future:nil)
(future? obj)
obj
:else
(completed obj))))
link
(f/future? (f/future:lift (Object.))) => true
future:now ^
[future] [future default]
gets the value of a future at the current moment
v 3.0
(defn future:now
([^CompletableFuture future]
(future:now future nil))
([^CompletableFuture future default]
(.getNow future default)))
link
(-> (f/future:run (fn [] (Thread/sleep 100))) (f/future:now :invalid)) => :invalid
future:obtrude ^
[future object] [future type object]
like force but uses obtrude and obtrudeException
v 4.0
(defn future:obtrude
([^CompletableFuture future object]
(future:obtrude future :value object))
([^CompletableFuture future type object]
(case type
:value (doto future (.obtrudeValue object))
:exception (doto future (.obtrudeException object)))))
link
(-> (f/future:run (fn [] (Thread/sleep 1000))) (f/future:obtrude 10) (f/future:value)) => 10
v 3.0
(defn future:result
([future]
(let [[status data exception]
(if (and (future:wait future)
(future:exception? future))
[:error nil (future:exception future)]
[:success (future:value future) nil])]
{:status status
:data data
:exception exception})))
link
(f/future:result (completed 1)) => {:status :success, :data 1, :exception nil} (f/future:result (failed (ex-info "" {}))) => (contains {:status :error :data nil :exception Throwable})
future:run ^
[obj] [obj {:keys [pool delay timeout default], :as m}]
runs a function with additional settings
v 3.0
(defn ^CompletableFuture future:run
([obj]
(future:run obj {}))
([obj {:keys [pool delay timeout default] :as m}]
(-> (future:call obj m)
(future:timeout timeout default))))
link
(f/future:run (fn [] (Thread/sleep 100)) {:timeout 10 :default :ok :delay 100 :pool :async}) => any?
future:status ^
[future]
retrieves a status of either `:success`, `:error` or `waiting`
v 3.0
(defn future:status
([future]
(cond (not (future-done? future))
:waiting
(future:exception? future)
:error
:else :success)))
link
(f/future:status (completed 1)) => :success
v 3.0
(defn future:success?
([^CompletableFuture future]
(future:done future (not (.isCompletedExceptionally future)))))
link
(-> (f/future:run (fn [] (Thread/sleep 100))) (f/future:wait) (f/future:success?)) => true (-> (f/future:run (fn [] (Thread/sleep 100))) (f/future:success?)) => (throws)
future:timeout ^
[future interval] [future interval default]
adds a timeout to the completed future
v 3.0
(defn ^CompletableFuture future:timeout
([^CompletableFuture future interval]
(future:timeout future interval nil))
([^CompletableFuture future interval default]
(cond (and (nil? interval)
(nil? default))
future
(nil? default)
(.orTimeout future interval TimeUnit/MILLISECONDS)
:else
(.completeOnTimeout future default interval TimeUnit/MILLISECONDS))))
link
@(-> (f/future:call (fn [] (Thread/sleep 100))) (f/future:timeout 10 :ok)) => :ok @(-> (f/future:call (fn [] (Thread/sleep 100))) (f/future:timeout 10)) => (throws)
v 3.0
(defn future:timeout?
([^CompletableFuture future]
(future:done future
(and (.isCompletedExceptionally future)
(instance? TimeoutException
(.getCause ^Throwable (future:exception future)))))))
link
(-> (f/future:run (fn [] (Thread/sleep 100))) (f/future:timeout 10) (f/future:wait) (f/future:timeout?)) => true
v 3.0
(defn future:value
([^CompletableFuture future]
(.get future)))
link
(-> (f/future:run (fn [] 1)) (f/future:value)) => 1
v 3.0
(defn ^CompletableFuture future:wait
([^CompletableFuture future]
(try (doto future (.get))
(catch Throwable t
future)))
([^CompletableFuture future ^long interval]
(Thread/sleep interval)
future))
link
(-> (f/future:call (fn [] (Thread/sleep 100))) (f/future:timeout 10 :ok) (f/future:wait) (f/future:now)) => :ok
on:all ^
[futures] [futures f] [futures f {:keys [timeout pool default delay], :as m}]
calls a function when all futures are complete
v 3.0
(defn ^CompletableFuture on:all
([futures]
(on:all futures vector))
([futures f]
(on:all futures f {}))
([futures f {:keys [timeout pool default delay] :as m}]
(let [futures (map future:lift futures)]
(cond-> (into-array CompletableFuture futures)
:then (CompletableFuture/allOf)
f (on:complete (fn [_ _]
(apply f (map future:value futures)))
m)))))
link
@(on:all [(f/future 1) (f/future 2) (f/future 3)] (fn [a b c] (+ a b c))) => 6
on:any ^
[futures f] [futures f {:keys [timeout pool default delay], :as m}]
calls a function when any future is completed
v 3.0
(defn ^CompletableFuture on:any
([futures f]
(on:any futures f {}))
([futures f {:keys [timeout pool default delay] :as m}]
(let [futures (map future:lift futures)
select (CompletableFuture/anyOf (into-array CompletableFuture
futures))]
(on:complete select
(fn [_ _]
(f (future:value select)))
m))))
link
@(on:any [(f/future (Thread/sleep 500) 1) (f/future (Thread/sleep 500) 2) (f/future 3)] (fn [a] a)) => any
v 3.0
(defn ^CompletableFuture on:cancel
([^CompletableFuture future f]
(on:cancel future f {}))
([^CompletableFuture future f m]
(on:complete future (exception-fn
(fn [e]
(if (and e (instance? CancellationException e))
(f e)
(throw e))))
m)))
link
@(-> (f/future {:timeout 10} (Thread/sleep 100)) (f/future:cancel) (on:cancel (fn [_] :cancel))) => :cancel @(-> (f/future {:timeout 10} (Thread/sleep 100)) (f/future:wait) (on:cancel (fn [_] :cancel))) => (throws)
on:complete ^
[future f] [future f {:keys [timeout pool default delay], :as m}]
process both the value and exception
v 3.0
(defn ^CompletableFuture on:complete
([^CompletableFuture future f]
(on:complete future f {}))
([^CompletableFuture future f {:keys [timeout pool default delay] :as m}]
(let [bin-fn (fn/fn:lambda [success error]
(f success (if error (or (.getCause ^Throwable error)
error))))
executor (if pool
(if (keyword? pool)
(get @*pools* pool)
pool))
executor (if delay
(CompletableFuture/delayedExecutor delay
TimeUnit/MILLISECONDS
(or executor (:default @*pools*)))
executor)
nfuture (cond (nil? executor)
(.handle future
bin-fn)
:else (.handleAsync future bin-fn ^Executor executor))]
(future:timeout nfuture timeout default))))
link
@(-> (f/future 1) (on:complete (fn [val _] (throw (ex-info "Error" {:val (inc val)})))) (on:complete (fn [_ err] (inc (:val (ex-data err))))) (on:complete (fn [val _] (inc val)))) => 4
on:exception ^
[future f] [future f {:keys [timeout pool default delay], :as m}]
process a function on exception
v 3.0
(defn ^CompletableFuture on:exception
([^CompletableFuture future f]
(on:exception future f {}))
([^CompletableFuture future f {:keys [timeout pool default delay] :as m}]
(on:complete future (exception-fn (fn [e] (f e)))
m)))
link
@(-> (f/future {:timeout 10} (Thread/sleep 100)) (f/future:cancel) (on:exception (fn [_] :cancel))) => :cancel @(-> (f/future {:timeout 10} (Thread/sleep 100)) (f/future:wait) (on:exception (fn [_] :timeout))) => :timeout @(-> (f/future (throw (ex-info "Error" {}))) (f/future:wait) (on:exception (fn [_] :exception))) => :exception
on:success ^
[future f] [future f {:keys [timeout pool default delay], :as m}]
processes another step given successful operation
v 3.0
(defn ^CompletableFuture on:success
([^CompletableFuture future f]
(on:success future f {}))
([^CompletableFuture future f {:keys [timeout pool default delay] :as m}]
(on:complete future (fn [v e]
(if e (throw e) (f v)))
m)))
link
@(-> (f/future 1) (on:success inc) (on:success inc) (on:success inc)) => 4
on:timeout ^
[future f] [future f {:keys [timeout pool default delay], :as m}]
processes a function on timeout
v 3.0
(defn ^CompletableFuture on:timeout
([^CompletableFuture future f]
(on:timeout future f {}))
([^CompletableFuture future f {:keys [timeout pool default delay] :as m}]
(on:complete future (exception-fn
(fn [e]
(if (and e (instance? TimeoutException e))
(f e)
(throw e))))
m)))
link
(-> (f/future (Thread/sleep 100)) (f/future:timeout 10) (on:timeout (fn [_] :timeout)) (f/future:wait) (f/future:value)) => :timeout @(-> (f/future {:timeout 10} (Thread/sleep 100)) (f/future:cancel) (on:timeout (fn [_] :timeout))) => (throws)
v 3.0
(defmacro then
([future bindings & body]
(let [len (count bindings)]
(case len
0 `(on:success ~future (fn [~'_] ~@body))
1 `(on:success ~future (fn [~@bindings] ~@body))
2 `(on:complete ~future (fn [~@bindings] ~@body))))))
link
@(-> (f/future (+ 1 2 3)) (then [a] (+ a 1 2 3))) => 12 @(-> (f/future (throw (ex-info "Error" {:data 1}))) (then [a] (+ a 1 2 3)) (f/catch [ex] (ex-data ex))) => {:data 1} @(-> (f/future (+ 1 2 3)) (then [_ err] err)) => nil
4 std.lib.future: A Comprehensive Summary
The std.lib.future namespace provides a powerful and flexible abstraction for asynchronous and concurrent programming in Clojure, built upon Java's CompletableFuture. It offers a rich set of functions and macros for creating, managing, composing, and handling the results of asynchronous computations, including features for timeouts, cancellations, and exception handling.
Key Features and Concepts:
- Core Future Creation and Management:n
*pools*: A delayed atom holding a map ofjava.util.concurrent.Executorinstances (e.g.,:default,:pooled,:async) for executing futures.ncompleted [v]: Creates aCompletableFuturethat is already completed with a given valuev.nfailed [e]: Creates aCompletableFuturethat is already completed exceptionally with a givenThrowable e.nincomplete []: Creates aCompletableFuturethat is not yet completed, similar to aPromise.nfuture? [obj]: A predicate to check if an object is aCompletableFuture.nfuture:fn [f & [m]]: Creates aCompletableFuturethat holds a functionfand optional metadatam, intended for later execution.nfuture:call [obj & [m]]: Executes a functionobj(or afuture:fnobject) asynchronously, returning aCompletableFuture. It supports specifying an executorpooland adelay.nfuture:timeout [future interval & [default]]: Adds a timeout to aCompletableFuture. If the future doesn't complete withinintervalmilliseconds, it either completes exceptionally with aTimeoutExceptionor with adefaultvalue.nfuture:wait [future & [interval]]: Blocks the current thread until thefuturecompletes. Optionally waits for a specifiedintervalbefore returning the future.nfuture:run [obj & [m]]: A high-level function to run an asynchronous computation with options forpool,delay,timeout, anddefaultvalue.nfuture:now [future & [default]]: Attempts to get the value of thefutureimmediately without blocking. If not complete, returnsdefaultor throws an exception.nfuture:value [future]: Blocks and returns the result of thefuture.nfuture:exception [future]: Blocks and returns the exception if thefuturecompleted exceptionally, otherwisenil.nfuture:cancel [future]: Attempts to cancel the execution of thefuture.nfuture:done [future & body]: A macro that ensures afutureis complete before executingbody, otherwise throws an exception.nfuture:cancelled? [future]: Checks if thefuturewas cancelled.nfuture:exception? [future]: Checks if thefuturecompleted exceptionally.nfuture:timeout? [future]: Checks if thefuturecompleted exceptionally due to aTimeoutException.nfuture:success? [future]: Checks if thefuturecompleted successfully (not exceptionally).nfuture:incomplete? [future]: Checks if thefutureis still running (not yet completed).nfuture:complete? [future]: Checks if thefuturehas completed (either successfully or exceptionally).nfuture:force [future type object]: Forces afutureto complete with a givenobject(either as a:valueor an:exception).nfuture:obtrude [future type object]: Similar tofuture:forcebut usesobtrudeValueandobtrudeException, which can overwrite existing results.nfuture:dependents [future]: Returns the number of dependent stages waiting on thefuture's completion.nfuture:nil []: A memoized function that returns a completed future withnil.nfuture:lift [obj]: Lifts a plain value or an existingCompletableFutureinto aCompletableFuture.nn2. Future Composition and Callbacks (on:functions):non:complete [future f & [m]]: Attaches a callbackfto be executed when thefuturecompletes, regardless of success or failure. The callback receives both the result and any exception.non:timeout [future f & [m]]: Attaches a callbackfto be executed specifically if thefuturetimes out.non:cancel [future f & [m]]: Attaches a callbackfto be executed specifically if thefutureis cancelled.non:exception [future f & [m]]: Attaches a callbackfto be executed specifically if thefuturecompletes exceptionally (for any exception).non:success [future f & [m]]: Attaches a callbackfto be executed only if thefuturecompletes successfully.non:all [futures & [f m]]: Creates a newCompletableFuturethat completes when all inputfutureshave completed. An optional functionfcan process their results.non:any [futures f & [m]]: Creates a newCompletableFuturethat completes when any of the inputfuturescompletes. An optional functionfcan process the result of the first completed future.nn3. Macros for Asynchronous Flow Control:nfuture [& opts? body]: A macro that wrapsfuture:runto provide a more idiomatic way to define asynchronous blocks of code.nthen [future bindings & body]: A macro that provides a convenient syntax for chaining successful operations (on:success) or handling both success and failure (on:complete).ncatch [future bindings & body]: A macro that provides a convenient syntax for handling exceptions (on:exception).nn4. Result Handling and Status:nfulfil [future f & [print skip-success]]: Attempts to complete anincompletefuture by executing a functionf. It handles both successful results and exceptions, optionally printing stack traces.nfuture:result [future]: Returns a map describing the final status of afuture(:success,:error), itsdata, and anyexception.nfuture:status [future]: Returns a keyword indicating the current status of afuture(:waiting,:success,:error).nfuture:chain [future chain & [marr]]: Chains a sequence of functions (chain) to be executed sequentially on the result of afuture, each step returning a new future.
Overall Importance:
The std.lib.future module is a cornerstone for building responsive, scalable, and fault-tolerant applications within the foundation-base project. Its key contributions include:
- Simplified Asynchronous Programming: Provides a high-level, idiomatic Clojure API over
CompletableFuture, making it easier to write and reason about asynchronous code.n Robust Concurrency Control: Offers fine-grained control over execution (executors, delays, timeouts) and robust exception handling mechanisms.n Powerful Composition: Theon:functions andthen/catchmacros enable complex asynchronous workflows to be built by composing smaller, independent futures.n Improved Responsiveness: Allows long-running computations to be offloaded to background threads, preventing UI freezes or blocking of critical operations.n Error Resilience: Built-in support for timeouts, cancellations, and structured exception handling helps in creating more resilient systems.
By offering these comprehensive asynchronous programming capabilities, std.lib.future significantly enhances the foundation-base project's ability to manage complex, concurrent tasks, which is vital for its multi-language transpilation and runtime management goals.