Knitty ships with a high-performance implementation of Manifold-style deferreds. They implement the same public protocols as manifold.deferred
so they interoperate with the rest of the Manifold ecosystem, but are optimised for Knitty's execution engine and tracing.
Key differences compared to stock Manifold deferreds:
CompletableFuture
).bind
mirrors Manifold's '
variants).Most of the time you do not need to construct deferreds manually — Knitty runtime does that for you — but the helpers in knitty.deferred
are handy when integrating external asynchronous APIs or writing custom utilities.
(ns user
(:require [knitty.deferred :as kd]
[manifold.deferred :as md]))
(def empty-d (kd/create)) ;; unrealised placeholder
(def success-d (kd/wrap 42)) ;; realised value, accepts IDeferred or plain values
(def error-d (kd/wrap-err (Exception.))) ;; realised with an error
(def coerce-mf (kd/wrap (md/future 1))) ;; convert a manifold future
(def coerce-core (kd/wrap* (future 1))) ;; accepts futures/promises via md/->deferred
kd/success!
and kd/error!
mirror the Manifold helpers:
(def pending (kd/create))
(kd/success! pending :done)
@pending ;; => :done
(def other (kd/create))
(kd/listen! other #(println (kd/peel other)))
(kd/error! other (IllegalStateException. "nope"))
;; prints IllegalStateException
Use kd/peel
when you expect a deferred to be realised and want to obtain the value (with an optional default):
(kd/peel (kd/wrap 5)) ;; => 5
(kd/peel (kd/create) :timeout) ;; => :timeout
The core combinator is kd/bind
. It behaves like manifold.deferred/chain'
: it does not coerce return values and expects you to return an IDeferred
when chaining asynchronously.
(-> (kd/wrap 1)
(kd/bind (fn [x] (kd/wrap (inc x))))
(kd/bind (fn [x] (kd/wrap (* x 3)))))
;; => deferred that realises to 6
Convenience forms:
kd/bind-err
— catch errors, optionally filtering by class or predicate on ExceptionInfo
data.kd/bind-fnl
— finally handler; unlike Manifold it awaits the result of the callback.kd/bind->
— macro for piping multiple callbacks (->
semantics with automatic wrapping).kd/bind-onto
— run a callback on a specific executor.kd/bind=>
— threaded variant for building pipelines.All of these defer to the same lock-free core and follow the '
(no coercion) semantics from Manifold.
kd/revoke-to
Manifold's short-circuiting behaviour can be surprising when combined with cancellation. Knitty keeps kd/bind
simple — if the downstream deferred is cancelled or completed externally, upstream callbacks do not receive a cancellation signal automatically.
Use kd/revoke-to
to propagate cancellations/timeouts back to the original deferred:
(let [src (md/future (Thread/sleep 100) :ok)
derived (-> src
(kd/bind #(println "value:" %))
(kd/revoke-to src))]
(md/success! derived ::cancelled))
;; prints nothing; completion is routed back to `src`
You can combine it with kd/bind-err
(or Manifold's md/catch
) to ensure both success and failure paths are covered.
When bridging non-Manifold APIs, supply a custom cancellation callback via kd/revoke
:
(let [fut (future
(Thread/sleep 1000)
(println "Work...")
1)
wrapped (-> (kd/wrap* fut)
(kd/bind inc)
(kd/revoke #(do (future-cancel fut))))]
(kd/error! wrapped ::stop)
(assert (future-cancelled? fut)))
Knitty mirrors Manifold's structured helpers and adds sequential variants that avoid building a flow graph:
kd/let-bind
— sequential analogue to md/let-flow
with :let
and :when
clauses.kd/while
, kd/reduce
, kd/run!
— convenience macros that provide looping semantics.md/zip
and md/alt
that honour the same contracts but avoid repeated coercions.Refer to the docstrings in knitty.deferred
for detailed argument lists and behaviour differences.
knitty.deferred/*executor*
(used by yank
and kd/future
) defaults to a tuned ForkJoinPool
(async mode, named threads, error logging). You can replace it globally via knitty.core/set-executor!
or temporarily by passing :executor
when calling yank*
. Use knitty.deferred/build-fork-join-pool
if you need a customised pool (naming, saturation checks, etc.).
Scheduled tasks use *sched-executor*
, a ScheduledThreadPoolExecutor
that preserves dynamic bindings while executing timers.
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 |