UNPKG

5.25 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 =
123 !observerOrOnValue || typeof observerOrOnValue === 'function'
124 ? {value: observerOrOnValue, error: onError, end: onEnd}
125 : observerOrOnValue
126
127 const handler = function(event) {
128 if (event.type === END) {
129 closed = true
130 }
131 if (event.type === VALUE && observer.value) {
132 observer.value(event.value)
133 } else if (event.type === ERROR && observer.error) {
134 observer.error(event.value)
135 } else if (event.type === END && observer.end) {
136 observer.end(event.value)
137 }
138 }
139
140 this.onAny(handler)
141
142 return {
143 unsubscribe() {
144 if (!closed) {
145 _this.offAny(handler)
146 closed = true
147 }
148 },
149 get closed() {
150 return closed
151 },
152 }
153 },
154
155 // A and B must be subclasses of Stream and Property (order doesn't matter)
156 _ofSameType(A, B) {
157 return A.prototype.getType() === this.getType() ? A : B
158 },
159
160 setName(sourceObs /* optional */, selfName) {
161 this._name = selfName ? `${sourceObs._name}.${selfName}` : sourceObs
162 return this
163 },
164
165 log(name = this.toString()) {
166 let isCurrent
167 let handler = function(event) {
168 let type = `<${event.type}${isCurrent ? ':current' : ''}>`
169 if (event.type === END) {
170 console.log(name, type)
171 } else {
172 console.log(name, type, event.value)
173 }
174 }
175
176 if (this._alive) {
177 if (!this._logHandlers) {
178 this._logHandlers = []
179 }
180 this._logHandlers.push({name: name, handler: handler})
181 }
182
183 isCurrent = true
184 this.onAny(handler)
185 isCurrent = false
186
187 return this
188 },
189
190 offLog(name = this.toString()) {
191 if (this._logHandlers) {
192 let handlerIndex = findByPred(this._logHandlers, obj => obj.name === name)
193 if (handlerIndex !== -1) {
194 this.offAny(this._logHandlers[handlerIndex].handler)
195 this._logHandlers.splice(handlerIndex, 1)
196 }
197 }
198
199 return this
200 },
201
202 spy(name = this.toString()) {
203 let handler = function(event) {
204 let type = `<${event.type}>`
205 if (event.type === END) {
206 console.log(name, type)
207 } else {
208 console.log(name, type, event.value)
209 }
210 }
211 if (this._alive) {
212 if (!this._spyHandlers) {
213 this._spyHandlers = []
214 }
215 this._spyHandlers.push({name: name, handler: handler})
216 this._dispatcher.addSpy(handler)
217 }
218 return this
219 },
220
221 offSpy(name = this.toString()) {
222 if (this._spyHandlers) {
223 let handlerIndex = findByPred(this._spyHandlers, obj => obj.name === name)
224 if (handlerIndex !== -1) {
225 this._dispatcher.removeSpy(this._spyHandlers[handlerIndex].handler)
226 this._spyHandlers.splice(handlerIndex, 1)
227 }
228 }
229 return this
230 },
231})
232
233// extend() can't handle `toString` in IE8
234Observable.prototype.toString = function() {
235 return `[${this._name}]`
236}
237
238export default Observable