git ssb

1+

Matt McKegg / mutant



Tree: 5be33c01238870a66c608fed4ece3fbc43069a51

Files: 5be33c01238870a66c608fed4ece3fbc43069a51 / computed.js

3780 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) {
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
27 var live = false
28 var lazy = false
29 var initialized = false
30 var listeners = []
31
32 var result = function (listener) {
33 if (!listener) {
34 return getValue()
35 }
36
37 if (typeof listener !== 'function') {
38 throw new Error('Listeners must be functions.')
39 }
40
41 listeners.push(listener)
42 listen()
43
44 return function remove () {
45 for (var i = 0, len = listeners.length; i < len; i++) {
46 if (listeners[i] === listener) {
47 listeners.splice(i, 1)
48 break
49 }
50 }
51 if (!listeners.length) {
52 unlisten()
53 }
54 }
55 }
56
57 return result
58
59 // scoped
60
61 function listen () {
62 if (!live) {
63 for (var i = 0, len = observables.length; i < len; i++) {
64 if (isObservable(observables[i])) {
65 releases.push(observables[i](onUpdate))
66 }
67 }
68 if (inner) {
69 releaseInner = inner(onInnerUpdate)
70 }
71 live = true
72 lazy = true
73 }
74 }
75
76 function unlisten () {
77 if (live) {
78 live = false
79
80 if (releaseInner) {
81 releaseInner()
82 releaseInner = null
83 }
84
85 while (releases.length) {
86 releases.pop()()
87 }
88 }
89 }
90
91 function update () {
92 var changed = false
93 for (var i = 0, len = observables.length; i < len; i++) {
94 var newValue = resolve(observables[i])
95 if (newValue !== values[i] || isReferenceType(newValue)) {
96 changed = true
97 values[i] = newValue
98 }
99 }
100
101 if (changed || !initialized) {
102 initialized = true
103 var newComputedValue = lambda.apply(null, values)
104
105 if (newComputedValue === computed.NO_CHANGE) {
106 return false
107 }
108
109 if (newComputedValue !== computedValue || (isReferenceType(newComputedValue) && !isObservable(newComputedValue))) {
110 if (releaseInner) {
111 releaseInner()
112 inner = releaseInner = null
113 }
114
115 if (isObservable(newComputedValue)) {
116 // handle returning observable from computed
117 computedValue = newComputedValue()
118 inner = newComputedValue
119 if (live) {
120 releaseInner = inner(onInnerUpdate)
121 }
122 } else {
123 computedValue = newComputedValue
124 }
125 return true
126 }
127 }
128 return false
129 }
130
131 function onInnerUpdate (value) {
132 if (value !== computedValue || isReferenceType(computedValue)) {
133 computedValue = value
134 broadcast(listeners, computedValue)
135 }
136 }
137
138 function onUpdate () {
139 if (update()) {
140 broadcast(listeners, computedValue)
141 }
142 }
143
144 function getValue () {
145 if (!live || lazy) {
146 lazy = false
147 update()
148 }
149 return computedValue
150 }
151}
152
153function isReferenceType (value) {
154 return typeof value === 'object' && value !== null
155}
156
157function broadcast (listeners, value) {
158 // cache listeners in case modified during broadcast
159 listeners = listeners.slice(0)
160 for (var i = 0, len = listeners.length; i < len; i++) {
161 listeners[i](value)
162 }
163}
164

Built with git-ssb-web