mirror of
https://github.com/status-im/re-frame.git
synced 2025-02-23 15:28:09 +00:00
Introduce de-duplicated subscriptions.
This commit is contained in:
parent
32ab35a436
commit
3d4e8cde8f
33
CHANGES.md
33
CHANGES.md
@ -1,11 +1,38 @@
|
||||
## 0.8.0 (XXX)
|
||||
|
||||
Headline:
|
||||
- re-frame subscriptions are now de-duplicated. As a result, some Signal graphs will be much more
|
||||
efficient. The new behaviour better matches programmer intuitions about what "should" happen.
|
||||
|
||||
*Explanation*
|
||||
|
||||
Each subscription causes a handler to execute, producing
|
||||
a reactive stream of updates. Two calls to `(subscribe [:some :query])` results in two copies of the same
|
||||
subscription handler running, each delivering a stream of updates. Now, if these two subscriptions
|
||||
were running at the same time, this would be inefficient. Both handlers would be
|
||||
doing the same computations and delivering the same stream of updates. Unnecessary, duplicate work.
|
||||
|
||||
Starting with this version, this sort of duplication has been eliminated. Two, or more, concurrent
|
||||
subscriptions for the same query will now source reactive updates from the one executing handler.
|
||||
|
||||
So, how do we know if two subscriptions are "the same". Answer: two subscriptions
|
||||
are the same if their query vectors test `=` to each other.
|
||||
|
||||
So, these two subscriptions are *not* "the same": `[:some-event 42]` `[:some-event "blah"]`. Even
|
||||
though they involve the same event id, `:some-event`, the query vectors do not test `=`.
|
||||
|
||||
|
||||
Breaking:
|
||||
- this version requires reagent 0.6.0 or later. It won't work with 0.5.N.
|
||||
- requires Reagent >= 0.6.0 or later. It won't work with <= Reagent 0.5.2.
|
||||
- XXX undo extracted and put into sister library
|
||||
|
||||
Improvements
|
||||
- `debug` middleware logs a single log line instead of a group if there is no difference in app-db between
|
||||
before and after running the handler.
|
||||
- XXXX middleware for spec checking of event vectors
|
||||
- XXXX better subscriptions of subscriptions
|
||||
- XXX spec definitions of what subscriptions deliver ??
|
||||
- when an event-handler makes no change to `app-db`, the `debug` middleware now logs a
|
||||
single line saying so, rather than a "group". Makes it slightly easier to grok
|
||||
the absence of change.
|
||||
|
||||
## 0.7.0 (2016-03-14)
|
||||
|
||||
|
@ -1,46 +1,85 @@
|
||||
(ns re-frame.subs
|
||||
(:require
|
||||
[reagent.ratom :refer [make-reaction] :refer-macros [reaction run!]]
|
||||
[reagent.ratom :as ratom :refer [make-reaction] :refer-macros [reaction]]
|
||||
[re-frame.db :refer [app-db]]
|
||||
[re-frame.utils :refer [first-in-vector warn error]]))
|
||||
|
||||
|
||||
;; maps from handler-id to handler-fn
|
||||
(def ^:private key->fn (atom {}))
|
||||
;; -- Subscription Handler Lookup and Registration --------------------------------------------------
|
||||
|
||||
;; Maps from a query-id to a handler function.
|
||||
(def ^:private qid->fn (atom {}))
|
||||
|
||||
(defn clear-handlers!
|
||||
"Unregisters all subscription handlers"
|
||||
"Unregisters all existing subscription handlers"
|
||||
[]
|
||||
(reset! key->fn {}))
|
||||
|
||||
(reset! qid->fn {}))
|
||||
|
||||
(defn register
|
||||
"Registers a handler function for an id"
|
||||
[key-v handler-fn]
|
||||
(if (contains? @key->fn key-v)
|
||||
(warn "re-frame: overwriting subscription-handler for: " key-v)) ;; allow it, but warn.
|
||||
(swap! key->fn assoc key-v handler-fn))
|
||||
"Registers a subscription handler function for an query id"
|
||||
[query-id handler-fn]
|
||||
(if (contains? @qid->fn query-id)
|
||||
(warn "re-frame: overwriting subscription handler for: " query-id)) ;; allow it, but warn. Happens on figwheel reloads.
|
||||
(swap! qid->fn assoc query-id handler-fn))
|
||||
|
||||
|
||||
;; -- Subscription cache -----------------------------------------------------
|
||||
;;
|
||||
;; De-duplicate subscriptions. If two or more identical subscriptions
|
||||
;; are concurrently active, we want only one handler running.
|
||||
;; Two subscriptions are "identical" if their query vectors
|
||||
;; test "=".
|
||||
(def ^:private query->reaction (atom {}))
|
||||
|
||||
(defn cache-and-return
|
||||
"cache the reaction r"
|
||||
[v dynv r]
|
||||
(let [cache-key [v dynv]]
|
||||
;; when this reaction is nolonger being used, remove it from the cache
|
||||
(ratom/add-on-dispose! r #(do (swap! query->reaction dissoc cache-key)
|
||||
(warn "Removing subscription: " cache-key)))
|
||||
|
||||
;; cache this reaction, so it can be used for identical subscriptions
|
||||
(swap! query->reaction assoc cache-key r)
|
||||
|
||||
r)) ;; return the actual reaction
|
||||
|
||||
(defn cache-lookup
|
||||
([query-v]
|
||||
(cache-lookup query-v []))
|
||||
([query-v dyn-v]
|
||||
(get @query->reaction [query-v dyn-v])))
|
||||
|
||||
|
||||
;; -- Subscription cache -----------------------------------------------------
|
||||
|
||||
(defn subscribe
|
||||
"Returns a reagent/reaction which observes a part of app-db"
|
||||
"Returns a Reagent/reaction which contains a computation"
|
||||
([v]
|
||||
(let [key-v (first-in-vector v)
|
||||
handler-fn (get @key->fn key-v)]
|
||||
(if (nil? handler-fn)
|
||||
(error "re-frame: no subscription handler registered for: \"" key-v "\". Returning a nil subscription.")
|
||||
(handler-fn app-db v))))
|
||||
(if-let [cached (cache-lookup v)]
|
||||
(do (warn "Using cached subscription: " v)
|
||||
cached)
|
||||
(let [query-id (first-in-vector v)
|
||||
handler-fn (get @qid->fn query-id)]
|
||||
(warn "Subscription crerated: " v)
|
||||
(if-not handler-fn
|
||||
(error "re-frame: no subscription handler registered for: \"" query-id "\". Returning a nil subscription."))
|
||||
(cache-and-return v [] (handler-fn app-db v)))))
|
||||
|
||||
([v dynv]
|
||||
(let [key-v (first-in-vector v)
|
||||
handler-fn (get @key->fn key-v)]
|
||||
(when ^boolean js/goog.DEBUG
|
||||
(when-let [not-reactive (seq (remove #(implements? reagent.ratom/IReactiveAtom %) dynv))]
|
||||
(warn "re-frame: dynv contained parameters that don't implement IReactiveAtom: " not-reactive)))
|
||||
(if (nil? handler-fn)
|
||||
(error "re-frame: no subscription handler registered for: \"" key-v "\". Returning a nil subscription.")
|
||||
(let [dyn-vals (reaction (mapv deref dynv))
|
||||
sub (reaction (handler-fn app-db v @dyn-vals))]
|
||||
;; handler-fn returns a reaction which is then wrapped in the sub reaction
|
||||
;; need to double deref it to get to the actual value.
|
||||
(reaction @@sub))))))
|
||||
(if-let [cached (cache-lookup v dynv)]
|
||||
(do (warn "Using cached subscription: " v " and " dynv)
|
||||
cached)
|
||||
(let [query-id (first-in-vector v)
|
||||
handler-fn (get @qid->fn query-id)]
|
||||
(when ^boolean js/goog.DEBUG
|
||||
(when-let [not-reactive (remove #(implements? reagent.ratom/IReactiveAtom %) dynv)]
|
||||
(warn "re-frame: your subscription's dynamic parameters that don't implement IReactiveAtom: " not-reactive)))
|
||||
(if (nil? handler-fn)
|
||||
(error "re-frame: no subscription handler registered for: \"" query-id "\". Returning a nil subscription.")
|
||||
(let [dyn-vals (reaction (mapv deref dynv))
|
||||
sub (reaction (handler-fn app-db v @dyn-vals))]
|
||||
;; handler-fn returns a reaction which is then wrapped in the sub reaction
|
||||
;; need to double deref it to get to the actual value.
|
||||
(warn "Subscription created: " v dynv)
|
||||
(cache-and-return v dynv (reaction @@sub))))))))
|
||||
|
Loading…
x
Reference in New Issue
Block a user