UNPKG

5.24 kBJavaScriptView Raw
1import {extend} from './utils/objects'
2import {VALUE, ERROR, ANY, END} from './constants'
3import {Dispatcher, callSubscriber} from './dispatcher'
4import {findByPred} from './utils/collections'
5
6function Observable() {
7 this._dispatcher = new Dispatcher()
8 this._active = false
9 this._alive = true
10 this._activating = false
11 this._logHandlers = null
12 this._spyHandlers = null
13}
14
15extend(Observable.prototype, {
16 _name: 'observable',
17
18 _onActivation() {},
19 _onDeactivation() {},
20
21 _setActive(active) {
22 if (this._active !== active) {
23 this._active = active
24 if (active) {
25 this._activating = true
26 this._onActivation()
27 this._activating = false
28 } else {
29 this._onDeactivation()
30 }
31 }
32 },
33
34 _clear() {
35 this._setActive(false)
36 this._dispatcher.cleanup()
37 this._dispatcher = null
38 this._logHandlers = null
39 },
40
41 _emit(type, x) {
42 switch (type) {
43 case VALUE:
44 return this._emitValue(x)
45 case ERROR:
46 return this._emitError(x)
47 case END:
48 return this._emitEnd()
49 }
50 },
51
52 _emitValue(value) {
53 if (this._alive) {
54 this._dispatcher.dispatch({type: VALUE, value})
55 }
56 },
57
58 _emitError(value) {
59 if (this._alive) {
60 this._dispatcher.dispatch({type: ERROR, value})
61 }
62 },
63
64 _emitEnd() {
65 if (this._alive) {
66 this._alive = false
67 this._dispatcher.dispatch({type: END})
68 this._clear()
69 }
70 },
71
72 _on(type, fn) {
73 if (this._alive) {
74 this._dispatcher.add(type, fn)
75 this._setActive(true)
76 } else {
77 callSubscriber(type, fn, {type: END})
78 }
79 return this
80 },
81
82 _off(type, fn) {
83 if (this._alive) {
84 let count = this._dispatcher.remove(type, fn)
85 if (count === 0) {
86 this._setActive(false)
87 }
88 }
89 return this
90 },
91
92 onValue(fn) {
93 return this._on(VALUE, fn)
94 },
95 onError(fn) {
96 return this._on(ERROR, fn)
97 },
98 onEnd(fn) {
99 return this._on(END, fn)
100 },
101 onAny(fn) {
102 return this._on(ANY, fn)
103 },
104
105 offValue(fn) {
106 return this._off(VALUE, fn)
107 },
108 offError(fn) {
109 return this._off(ERROR, fn)
110 },
111 offEnd(fn) {
112 return this._off(END, fn)
113 },
114 offAny(fn) {
115 return this._off(ANY, fn)
116 },
117
118 observe(observerOrOnValue, onError, onEnd) {
119 const _this = this
120 let closed = false
121
122 const observer = !observerOrOnValue || typeof observerOrOnValue === 'function'
123 ? {value: observerOrOnValue, error: onError, end: onEnd}
124 : observerOrOnValue
125
126 const handler = function(event) {
127 if (event.type === END) {
128 closed = true
129 }
130 if (event.type === VALUE && observer.value) {
131 observer.value(event.value)
132 } else if (event.type === ERROR && observer.error) {
133 observer.error(event.value)
134 } else if (event.type === END && observer.end) {
135 observer.end(event.value)
136 }
137 }
138
139 this.onAny(handler)
140
141 return {
142 unsubscribe() {
143 if (!closed) {
144 _this.offAny(handler)
145 closed = true
146 }
147 },
148 get closed() {
149 return closed
150 },
151 }
152 },
153
154 // A and B must be subclasses of Stream and Property (order doesn't matter)
155 _ofSameType(A, B) {
156 return A.prototype.getType() === this.getType() ? A : B
157 },
158
159 setName(sourceObs /* optional */, selfName) {
160 this._name = selfName ? `${sourceObs._name}.${selfName}` : sourceObs
161 return this
162 },
163
164 log(name = this.toString()) {
165 let isCurrent
166 let handler = function(event) {
167 let type = `<${event.type}${isCurrent ? ':current' : ''}>`
168 if (event.type === END) {
169 console.log(name, type)
170 } else {
171 console.log(name, type, event.value)
172 }
173 }
174
175 if (this._alive) {
176 if (!this._logHandlers) {
177 this._logHandlers = []
178 }
179 this._logHandlers.push({name: name, handler: handler})
180 }
181
182 isCurrent = true
183 this.onAny(handler)
184 isCurrent = false
185
186 return this
187 },
188
189 offLog(name = this.toString()) {
190 if (this._logHandlers) {
191 let handlerIndex = findByPred(this._logHandlers, obj => obj.name === name)
192 if (handlerIndex !== -1) {
193 this.offAny(this._logHandlers[handlerIndex].handler)
194 this._logHandlers.splice(handlerIndex, 1)
195 }
196 }
197
198 return this
199 },
200
201 spy(name = this.toString()) {
202 let handler = function(event) {
203 let type = `<${event.type}>`
204 if (event.type === END) {
205 console.log(name, type)
206 } else {
207 console.log(name, type, event.value)
208 }
209 }
210 if (this._alive) {
211 if (!this._spyHandlers) {
212 this._spyHandlers = []
213 }
214 this._spyHandlers.push({name: name, handler: handler})
215 this._dispatcher.addSpy(handler)
216 }
217 return this
218 },
219
220 offSpy(name = this.toString()) {
221 if (this._spyHandlers) {
222 let handlerIndex = findByPred(this._spyHandlers, obj => obj.name === name)
223 if (handlerIndex !== -1) {
224 this._dispatcher.removeSpy(this._spyHandlers[handlerIndex].handler)
225 this._spyHandlers.splice(handlerIndex, 1)
226 }
227 }
228 return this
229 },
230})
231
232// extend() can't handle `toString` in IE8
233Observable.prototype.toString = function() {
234 return `[${this._name}]`
235}
236
237export default Observable