ClojureDocs

Nav

Namespaces

add-watch

clojure.core

Available since 1.0 (source)
  • (add-watch reference key fn)
Adds a watch function to an agent/atom/var/ref reference. The watch
fn must be a fn of 4 args: a key, the reference, its old-state, its
new-state. Whenever the reference's state might have been changed,
any registered watches will have their functions called. The watch fn
will be called synchronously, on the agent's thread if an agent,
before any pending sends if agent or ref. Note that an atom's or
ref's state may have changed again prior to the fn call, so use
old/new-state rather than derefing the reference. Note also that watch
fns may be called from multiple threads simultaneously. Var watchers
are triggered only by root binding changes, not thread-local
set!s. Keys must be unique per reference, and can be used to remove
the watch with remove-watch, but are otherwise considered opaque by
the watch mechanism.
5 Examples
;; Add useful context to watcher function:
(defn watch-agent [_agent context]
    (let [watch-fn (fn [_context _key _ref old-value new-value] ;...
               )] 
        (add-watch _agent nil (partial watch-fn context))))
Notice that it is nondeterministic that return happens first
or the `print` call happens first. (because they are happening in different threads)
 
user> (def a (agent 0))
=> #'user/a
user> a
=> #agent[{:status :ready, :val 0} 0x591e4e8e]
user> (add-watch a :key (fn [k r os ns] (print k r os ns)))
=> #agent[{:status :ready, :val 0} 0x591e4e8e]
user> (send a inc)
=> #agent[{:status :ready, :val 1} 0x591e4e8e]
:key #agent[{:status :ready, :val 1} 0x591e4e8e] 0 1
user> (send a inc)
:key #agent[{:status :ready, :val 2} 0x591e4e8e] 1 2
=> #agent[{:status :ready, :val 2} 0x591e4e8e]
(def a (atom {}))

(add-watch a :watcher
  (fn [key atom old-state new-state]
    (prn "-- Atom Changed --")
    (prn "key" key)
    (prn "atom" atom)
    (prn "old-state" old-state)
    (prn "new-state" new-state)))

(reset! a {:foo "bar"})

;; "-- Atom Changed --"
;; "key" :watcher
;; "atom" #<Atom@4b020acf: {:foo "bar"}>
;; "old-state" {}
;; "new-state" {:foo "bar"}
;; {:foo "bar"}
;; The name of my account can change, and I want to update another atom accordingly.
;; I just take the fourth argument that contains the new state and I ignore the other arguments.

(let [account (atom {:name "pending" 
                     :funds 100.50 
                     :profit-loss 23.45})
      label-account-name (atom "no-name-yet")]
   (add-watch account :listener-one #(reset! label-account-name (:name %4)))
   (println "Before swap:" @label-account-name)
   (swap! account assoc :name "CFD")
   (println "After swap:" @label-account-name))

;; Before swap: no-name-yet
;; After swap: CFD
;; ClojureScript: Log the new value of the ref to console whenever it changes

(def a (atom nil))

(add-watch a :logger #(-> %4 clj->js js/console.log))

(reset! a (my-app/initial-state))
See Also

Removes a watch (set by add-watch) from a reference

Added by pjlegato
1 Note
    By , created 9.7 years ago

    One "gotcha" is that atoms add-watches do not always guarantee order semantics. So if you have a watch put [old-val new-val] on a channel, and your atom operation is something like (swap! a inc), you may see values in your channel like this:

    [0 1]
    [2 3]
    [1 2]
    [3 4]
    [6 7]
    [4 5]
    

    This is because the watches are dispatched in different threads.

    Source