git ssb

1+

Matt McKegg / mutant



Tree: 955ee4ba0914a6c2c9a1158bdfa7ea7a186ca900

Files: 955ee4ba0914a6c2c9a1158bdfa7ea7a186ca900 / computed.js

3691 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 for (var i = 0, len = listeners.length; i < len; i++) {
135 listeners[i](computedValue)
136 }
137 }
138 }
139
140 function onUpdate () {
141 if (update()) {
142 for (var i = 0, len = listeners.length; i < len; i++) {
143 listeners[i](computedValue)
144 }
145 }
146 }
147
148 function getValue () {
149 if (!live || lazy) {
150 lazy = false
151 update()
152 }
153 return computedValue
154 }
155}
156
157function isReferenceType (value) {
158 return typeof value === 'object' && value !== null
159}
160

Built with git-ssb-web