Skip to content

Commit 367bfb4

Browse files
[added] IndefiniteSubject
Reviewers: O2 Material Motion, featherless Reviewed By: O2 Material Motion, featherless Subscribers: featherless Tags: #material_motion Differential Revision: http://codereview.cc/D2163
1 parent 9a09303 commit 367bfb4

11 files changed

Lines changed: 382 additions & 13 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
## Why? ##
77

8-
There are a lot of great Observable implementations, but they're baked into featureful libraries which contribute to both complexity and filesize. We wanted the simplest-possible Observable implementation, with no operators, no fancy scheduling: [read the entire source](https://github.com/material-motion/indefinite-observable-js/blob/develop/dist/index.js) without scrolling.
8+
There are a lot of great Observable implementations, but they're baked into featureful libraries which contribute to both complexity and filesize. We wanted the simplest-possible Observable implementation, with no operators, no fancy scheduling: [read the entire source](https://github.com/material-motion/indefinite-observable-js/blob/develop/src/IndefiniteObservable.ts) without scrolling.
99

1010
Indefinite Observable is a subset of the [TC39 Observable proposal](https://tc39.github.io/proposal-observable/) that never `complete`s or `error`s. It implements the [minimal-necessary functionality](https://en.wikipedia.org/wiki/You_aren't_gonna_need_it), but it should be completely interchangable with the TC39 proposal for the subset that it does implement.
1111

build.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,33 @@ const {
2121
writeFileSync,
2222
} = require('fs');
2323

24-
const source = readFileSync('./dist/index.js').toString();
24+
const observableSource = readFileSync('./dist/IndefiniteObservable.js').toString();
25+
const subjectSource = readFileSync('./dist/IndefiniteSubject.js').toString();
2526
const symbolObservable = readFileSync('./third_party/symbol-observable/index.js').toString();
2627

2728
writeFileSync(
2829
'./dist/indefinite-observable.js',
29-
source.replace(
30+
[
31+
observableSource,
32+
subjectSource,
33+
].join('\n\n').replace(
3034
/"use strict";\nconst symbol_observable_\d = require\("symbol-observable"\);/,
3135
symbolObservable
3236
).replace(
33-
/symbol_observable_\d\.default/,
37+
/"use strict";\nconst symbol_observable_\d = require\("symbol-observable"\);/,
38+
''
39+
).replace(
40+
// strip comments
41+
/\n^\s*\/\/.*$/mg,
42+
''
43+
).replace(
44+
/symbol_observable_\d\.default/g,
3445
'$$observable'
3546
).replace(
36-
/Object\.defineProperty\(exports, "__esModule", \{ value: true \}\);\nexports\.default = IndefiniteObservable;\n\/\/# sourceMappingURL=index\.js\.map/,
47+
/Object\.defineProperty\(exports, "__esModule", \{ value: true \}\);/g,
48+
''
49+
).replace(
50+
/exports\.default = \w+;/g,
3751
''
3852
)
3953
);

dist/IndefiniteSubject.d.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/** @license
2+
* Copyright 2016 - present The Material Motion Authors. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy
6+
* of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
import { Listener, Observer, Observable, Subscription } from './types';
17+
export default class IndefiniteSubject<T> implements Observable<T>, Observer {
18+
_observers: Set<Observer>;
19+
_lastValue: T;
20+
_hasStarted: boolean;
21+
next(value: T): void;
22+
subscribe(listener: Listener): Subscription;
23+
}

dist/IndefiniteSubject.js

Lines changed: 71 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/indefinite-observable.js

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
* under the License.
1515
*/
1616

17-
// for symbol-observable:
18-
/** @license
17+
18+
/** @license for symbol-observable:
1919
* The MIT License (MIT)
2020
*
2121
* Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
@@ -78,10 +78,6 @@ class IndefiniteObservable {
7878
this._creator = creator;
7979
}
8080
subscribe(listener) {
81-
// subscribe accepts next as either an anonymous function or as a named
82-
// member on an object. The creator always expects an object with a
83-
// function named next. Therefore, if we receive an anonymous function, we
84-
// wrap it in an object literal.
8581
if (!listener.next) {
8682
listener = {
8783
next: listener,
@@ -101,3 +97,65 @@ class IndefiniteObservable {
10197
return this;
10298
}
10399
}
100+
101+
102+
103+
/** @license
104+
* Copyright 2016 - present The Material Motion Authors. All Rights Reserved.
105+
*
106+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
107+
* use this file except in compliance with the License. You may obtain a copy
108+
* of the License at
109+
*
110+
* http://www.apache.org/licenses/LICENSE-2.0
111+
*
112+
* Unless required by applicable law or agreed to in writing, software
113+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
114+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
115+
* License for the specific language governing permissions and limitations
116+
* under the License.
117+
*/
118+
119+
class IndefiniteSubject {
120+
constructor() {
121+
this._observers = new Set();
122+
this._hasStarted = false;
123+
}
124+
next(value) {
125+
this._hasStarted = true;
126+
this._lastValue = value;
127+
this._observers.forEach((observer) => observer.next(value));
128+
}
129+
subscribe(listener) {
130+
const observer = wrapListenerWithObserver(listener);
131+
this._observers.add(observer);
132+
if (this._hasStarted) {
133+
observer.next(this._lastValue);
134+
}
135+
return {
136+
unsubscribe: () => {
137+
this._observers.delete(observer);
138+
}
139+
};
140+
}
141+
/**
142+
* Tells other libraries that know about observables that we are one.
143+
*
144+
* https://github.com/tc39/proposal-observable#observable
145+
*/
146+
[$observable]() {
147+
return this;
148+
}
149+
}
150+
151+
152+
function wrapListenerWithObserver(listener) {
153+
if (listener.next) {
154+
return listener;
155+
}
156+
else {
157+
return {
158+
next: listener
159+
};
160+
}
161+
}

dist/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,5 @@
1616
export * from './types';
1717
export * from './IndefiniteObservable';
1818
export { default as IndefiniteObservable } from './IndefiniteObservable';
19+
export * from './IndefiniteSubject';
20+
export { default as IndefiniteSubject } from './IndefiniteSubject';

dist/index.js

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/IndefiniteSubject.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/** @license
2+
* Copyright 2016 - present The Material Motion Authors. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy
6+
* of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
17+
import $$observable from 'symbol-observable';
18+
19+
import {
20+
Listener,
21+
Next,
22+
Observer,
23+
Observable,
24+
Subscription,
25+
Unsubscribe,
26+
} from './types';
27+
28+
export default class IndefiniteSubject<T> implements Observable<T>, Observer {
29+
// Keep track of all the observers who have subscribed, so we can notify them
30+
// when we get new values.
31+
_observers: Set<Observer> = new Set();
32+
_lastValue: T;
33+
_hasStarted: boolean = false;
34+
35+
next(value: T) {
36+
this._hasStarted = true;
37+
this._lastValue = value;
38+
39+
// The parent stream has dispatched a value, so pass it along to all the
40+
// children, and cache it for any observers that subscribe before the next
41+
// dispatch.
42+
this._observers.forEach(
43+
(observer: Observer) => observer.next(value)
44+
);
45+
}
46+
47+
subscribe(listener: Listener): Subscription {
48+
const observer = wrapListenerWithObserver(listener);
49+
50+
this._observers.add(observer);
51+
52+
if (this._hasStarted) {
53+
observer.next(this._lastValue);
54+
}
55+
56+
return {
57+
unsubscribe: () => {
58+
this._observers.delete(observer);
59+
}
60+
}
61+
}
62+
63+
/**
64+
* Tells other libraries that know about observables that we are one.
65+
*
66+
* https://github.com/tc39/proposal-observable#observable
67+
*/
68+
[$$observable](): Observable<T> {
69+
return this;
70+
}
71+
}
72+
73+
// TypeScript is a pain to use with polymorphic types unless you wrap them in a
74+
// function that returns a single type. So, that's what this is.
75+
//
76+
// If you give it an observer, you get back that observer. If you give it a
77+
// lambda, you get back that lambda wrapped in an observer.
78+
function wrapListenerWithObserver(listener: Listener): Observer {
79+
if ((listener as Observer).next) {
80+
return (listener as Observer);
81+
82+
} else {
83+
return {
84+
next: (listener as Next)
85+
}
86+
}
87+
}

0 commit comments

Comments
 (0)