Skip to content

Commit

Permalink
[added] IndefiniteSubject
Browse files Browse the repository at this point in the history
Reviewers: O2 Material Motion, featherless

Reviewed By: O2 Material Motion, featherless

Subscribers: featherless

Tags: #material_motion

Differential Revision: http://codereview.cc/D2163
  • Loading branch information
appsforartists committed Dec 8, 2016
1 parent 9a09303 commit 367bfb4
Show file tree
Hide file tree
Showing 11 changed files with 382 additions and 13 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

## Why? ##

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.
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.

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.

Expand Down
22 changes: 18 additions & 4 deletions build.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,33 @@ const {
writeFileSync,
} = require('fs');

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

writeFileSync(
'./dist/indefinite-observable.js',
source.replace(
[
observableSource,
subjectSource,
].join('\n\n').replace(
/"use strict";\nconst symbol_observable_\d = require\("symbol-observable"\);/,
symbolObservable
).replace(
/symbol_observable_\d\.default/,
/"use strict";\nconst symbol_observable_\d = require\("symbol-observable"\);/,
''
).replace(
// strip comments
/\n^\s*\/\/.*$/mg,
''
).replace(
/symbol_observable_\d\.default/g,
'$$observable'
).replace(
/Object\.defineProperty\(exports, "__esModule", \{ value: true \}\);\nexports\.default = IndefiniteObservable;\n\/\/# sourceMappingURL=index\.js\.map/,
/Object\.defineProperty\(exports, "__esModule", \{ value: true \}\);/g,
''
).replace(
/exports\.default = \w+;/g,
''
)
);
23 changes: 23 additions & 0 deletions dist/IndefiniteSubject.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/** @license
* Copyright 2016 - present The Material Motion Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import { Listener, Observer, Observable, Subscription } from './types';
export default class IndefiniteSubject<T> implements Observable<T>, Observer {
_observers: Set<Observer>;
_lastValue: T;
_hasStarted: boolean;
next(value: T): void;
subscribe(listener: Listener): Subscription;
}
71 changes: 71 additions & 0 deletions dist/IndefiniteSubject.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

70 changes: 64 additions & 6 deletions dist/indefinite-observable.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
* under the License.
*/

// for symbol-observable:
/** @license

/** @license for symbol-observable:
* The MIT License (MIT)
*
* Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Expand Down Expand Up @@ -78,10 +78,6 @@ class IndefiniteObservable {
this._creator = creator;
}
subscribe(listener) {
// subscribe accepts next as either an anonymous function or as a named
// member on an object. The creator always expects an object with a
// function named next. Therefore, if we receive an anonymous function, we
// wrap it in an object literal.
if (!listener.next) {
listener = {
next: listener,
Expand All @@ -101,3 +97,65 @@ class IndefiniteObservable {
return this;
}
}



/** @license
* Copyright 2016 - present The Material Motion Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

class IndefiniteSubject {
constructor() {
this._observers = new Set();
this._hasStarted = false;
}
next(value) {
this._hasStarted = true;
this._lastValue = value;
this._observers.forEach((observer) => observer.next(value));
}
subscribe(listener) {
const observer = wrapListenerWithObserver(listener);
this._observers.add(observer);
if (this._hasStarted) {
observer.next(this._lastValue);
}
return {
unsubscribe: () => {
this._observers.delete(observer);
}
};
}
/**
* Tells other libraries that know about observables that we are one.
*
* https://github.com/tc39/proposal-observable#observable
*/
[$observable]() {
return this;
}
}


function wrapListenerWithObserver(listener) {
if (listener.next) {
return listener;
}
else {
return {
next: listener
};
}
}
2 changes: 2 additions & 0 deletions dist/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@
export * from './types';
export * from './IndefiniteObservable';
export { default as IndefiniteObservable } from './IndefiniteObservable';
export * from './IndefiniteSubject';
export { default as IndefiniteSubject } from './IndefiniteSubject';
3 changes: 3 additions & 0 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

87 changes: 87 additions & 0 deletions src/IndefiniteSubject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/** @license
* Copyright 2016 - present The Material Motion Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

import $$observable from 'symbol-observable';

import {
Listener,
Next,
Observer,
Observable,
Subscription,
Unsubscribe,
} from './types';

export default class IndefiniteSubject<T> implements Observable<T>, Observer {
// Keep track of all the observers who have subscribed, so we can notify them
// when we get new values.
_observers: Set<Observer> = new Set();
_lastValue: T;
_hasStarted: boolean = false;

next(value: T) {
this._hasStarted = true;
this._lastValue = value;

// The parent stream has dispatched a value, so pass it along to all the
// children, and cache it for any observers that subscribe before the next
// dispatch.
this._observers.forEach(
(observer: Observer) => observer.next(value)
);
}

subscribe(listener: Listener): Subscription {
const observer = wrapListenerWithObserver(listener);

this._observers.add(observer);

if (this._hasStarted) {
observer.next(this._lastValue);
}

return {
unsubscribe: () => {
this._observers.delete(observer);
}
}
}

/**
* Tells other libraries that know about observables that we are one.
*
* https://github.com/tc39/proposal-observable#observable
*/
[$$observable](): Observable<T> {
return this;
}
}

// TypeScript is a pain to use with polymorphic types unless you wrap them in a
// function that returns a single type. So, that's what this is.
//
// If you give it an observer, you get back that observer. If you give it a
// lambda, you get back that lambda wrapped in an observer.
function wrapListenerWithObserver(listener: Listener): Observer {
if ((listener as Observer).next) {
return (listener as Observer);

} else {
return {
next: (listener as Next)
}
}
}
Loading

0 comments on commit 367bfb4

Please sign in to comment.