git ssb

1+

Matt McKegg / mutant



Tree: 994654fc520c8585e99de99665ac7140ce87a446

Files: 994654fc520c8585e99de99665ac7140ce87a446 / computed.js

4208 bytesRaw
1/* A lazy binding take on computed */
2// - doesn't start watching observables until itself is watched, and then releases if unwatched
3// - avoids memory/watcher leakage
4// - attaches to inner observables if these are returned from value
5// - doesn't broadcast if value is same as last value (and is `value type` or observable - can't make assuptions about reference types)
6// - doesn't broadcast if value is computed.NO_CHANGE
7
8var resolve = require('./resolve')
9var isObservable = require('./is-observable')
10
11module.exports = computed
12
13computed.NO_CHANGE = {}
14
15function computed (observables, lambda, opts) {
16 if (!Array.isArray(observables)) {
17 observables = [observables]
18 }
19
20 var values = []
21 var releases = []
22 var computedValue = null
23
24 var inner = null
25 var releaseInner = null
26 var updating = false
27
28 var live = false
29 var lazy = false
30 var initialized = false
31 var listeners = []
32
33 var result = function (listener) {
34 if (!listener) {
35 return getValue()
36 }
37
38 if (typeof listener !== 'function') {
39 throw new Error('Listeners must be functions.')
40 }
41
42 listeners.push(listener)
43 listen()
44
45 return function remove () {
46 for (var i = 0, len = listeners.length; i < len; i++) {
47 if (listeners[i] === listener) {
48 listeners.splice(i, 1)
49 break
50 }
51 }
52 if (!listeners.length) {
53 unlisten()
54 }
55 }
56 }
57
58 return result
59
60 // scoped
61
62 function listen () {
63 if (!live) {
64 for (var i = 0, len = observables.length; i < len; i++) {
65 if (isObservable(observables[i])) {
66 releases.push(observables[i](onUpdate))
67 }
68 }
69 if (inner) {
70 releaseInner = inner(onInnerUpdate)
71 }
72 live = true
73 lazy = true
74 }
75 }
76
77 function unlisten () {
78 if (live) {
79 live = false
80
81 if (releaseInner) {
82 releaseInner()
83 releaseInner = null
84 }
85
86 while (releases.length) {
87 releases.pop()()
88 }
89 }
90 }
91
92 function update () {
93 var changed = false
94 for (var i = 0, len = observables.length; i < len; i++) {
95 var newValue = resolve(observables[i])
96 if (newValue !== values[i] || isMutable(newValue)) {
97 changed = true
98 values[i] = newValue
99 }
100 }
101
102 if (changed || !initialized) {
103 initialized = true
104 var newComputedValue = lambda.apply(null, values)
105
106 if (newComputedValue === computed.NO_CHANGE) {
107 return false
108 }
109
110 if (newComputedValue !== computedValue || (isMutable(newComputedValue) && !isObservable(newComputedValue))) {
111 if (releaseInner) {
112 releaseInner()
113 inner = releaseInner = null
114 }
115
116 if (isObservable(newComputedValue)) {
117 // handle returning observable from computed
118 computedValue = newComputedValue()
119 inner = newComputedValue
120 if (live) {
121 releaseInner = inner(onInnerUpdate)
122 }
123 } else {
124 computedValue = newComputedValue
125 }
126 return true
127 }
128 }
129 return false
130 }
131
132 function onInnerUpdate (value) {
133 if (value !== computedValue || isMutable(computedValue)) {
134 computedValue = value
135 broadcast(listeners, computedValue)
136 }
137 }
138
139 function onUpdate () {
140 if (opts && opts.nextTick) {
141 if (!updating) {
142 updating = true
143 setImmediate(updateNow)
144 }
145 } else {
146 updateNow()
147 }
148 }
149
150 function updateNow () {
151 updating = false
152 if (update()) {
153 broadcast(listeners, computedValue)
154 }
155 }
156
157 function getValue () {
158 if (!live || lazy) {
159 lazy = false
160 update()
161 }
162 return computedValue
163 }
164
165 function isMutable (value) {
166 if (opts && opts.immutableTypes && opts.immutableTypes.some(type => value instanceof type)) {
167 return false
168 } else {
169 return isReferenceType(value)
170 }
171 }
172}
173
174function isReferenceType (value) {
175 return typeof value === 'object' && value !== null
176}
177
178function broadcast (listeners, value) {
179 // cache listeners in case modified during broadcast
180 listeners = listeners.slice(0)
181 for (var i = 0, len = listeners.length; i < len; i++) {
182 listeners[i](value)
183 }
184}
185

Built with git-ssb-web