git ssb

1+

Matt McKegg / mutant



Tree: 1964140bd2d8f29be42fb1ff7af768d7fb89ef59

Files: 1964140bd2d8f29be42fb1ff7af768d7fb89ef59 / computed.js

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

Built with git-ssb-web