Add empty-subcription detection to frptools

bump to v1.1.0
This commit is contained in:
Timothy Farrell 2017-10-27 21:37:57 -05:00
parent 6ce9ab7079
commit a26d268f8a
6 changed files with 118 additions and 9 deletions

View File

@ -40,10 +40,12 @@ inViewport(false);
### Subscribe to changes ### Subscribe to changes
Call the `subscribe` method with a callback that will be called when the observable is changed to a Call the `subscribe` method with a callback that will be called when the observable is changed to a
different value. The returned function can be called to unsubscribe from the observable. different value. The returned function can be called to unsubscribe from the observable. When called
it will provide the count of remaining subscriptions.
```js ```js
const unsubscribe = inViewport.subscribe(console.log.bind(console)); const unsubscribe = inViewport.subscribe(console.log.bind(console));
const remainingSubscriptionCount = unsubscribe();
``` ```
# [computed](./src/computed.js) # [computed](./src/computed.js)
@ -81,10 +83,12 @@ Call it to receive the stored value, recomputing if necessary.
### Subscribe to changes ### Subscribe to changes
Call the subscribe method with a callback that will be called when the computed result changes to a Call the subscribe method with a callback that will be called when the computed result changes to a
different value. The returned function can be called to unsubscribe from the observable. different value. The returned function can be called to unsubscribe from the observable. When called
it will provide the count of remaining subscriptions.
```js ```js
const unsubscribe = inViewport.subscribe(console.log.bind(console)); const unsubscribe = inViewport.subscribe(console.log.bind(console));
const remainingSubscriptionCount = unsubscribe();
``` ```
**NOTE**: Subscribing to a computed forces it to recompute every time an upstream dependency **NOTE**: Subscribing to a computed forces it to recompute every time an upstream dependency

View File

@ -1,6 +1,6 @@
{ {
"name": "frptools", "name": "frptools",
"version": "1.0.0", "version": "1.1.0",
"description": "Observable and Computed data streams", "description": "Observable and Computed data streams",
"main": "lib/index.js", "main": "lib/index.js",
"jsnext:main": "src/index.js", "jsnext:main": "src/index.js",

View File

@ -40,17 +40,107 @@ describe('computed', () => {
a(1); a(1);
expect(runCount).toEqual(0); expect(runCount).toEqual(0);
// b evaluates
expect(b()).toEqual(1); expect(b()).toEqual(1);
expect(runCount).toEqual(1); expect(runCount).toEqual(1);
// b does not evaluate
expect(b()).toEqual(1); expect(b()).toEqual(1);
expect(runCount).toEqual(1); expect(runCount).toEqual(1);
currentValue = 3; currentValue = 3;
// b does not evaluate
a(3); a(3);
expect(runCount).toEqual(1); expect(runCount).toEqual(1);
// b evaluates
expect(b()).toEqual(9); expect(b()).toEqual(9);
expect(runCount).toEqual(2); expect(runCount).toEqual(2);
}); });
it('computes automatically when subscribed', () => {
let runCount = 0;
let subRunCount = 0;
let currentValue = 1;
const a = observable(0);
const b = computed(
val => {
runCount += 1;
expect(val).toEqual(currentValue);
return val * val;
},
[a]
);
// b does not evaluate
a(1);
expect(runCount).toEqual(0);
// b evaluates
expect(b()).toEqual(1);
expect(runCount).toEqual(1);
// b does not evaluate
expect(b()).toEqual(1);
expect(runCount).toEqual(1);
const cancelSubscription = b.subscribe(val => {
subRunCount += 1;
expect(val).toEqual(currentValue * currentValue);
});
currentValue = 3;
// b evaluates
a(3);
expect(runCount).toEqual(2);
expect(subRunCount).toEqual(1);
// b does not evaluate
expect(b()).toEqual(9);
expect(runCount).toEqual(2);
expect(subRunCount).toEqual(1);
});
it('honors cancelled subscriptions', () => {
let runCount = 0;
let subRunCount = 0;
let currentValue = 1;
const a = observable(0);
const b = computed(
val => {
runCount += 1;
expect(val).toEqual(currentValue);
return val * val;
},
[a]
);
const cancelSubscription = b.subscribe(val => {
subRunCount += 1;
expect(val).toEqual(currentValue * currentValue);
});
const cancelSubscription2 = b.subscribe(val => {
subRunCount += 1;
});
// b evaluates
a(1);
expect(runCount).toEqual(1);
expect(subRunCount).toEqual(2);
// b does not evaluate
expect(b()).toEqual(1);
expect(runCount).toEqual(1);
expect(subRunCount).toEqual(2);
expect(cancelSubscription()).toEqual(1);
currentValue = 3;
// b evaluates
a(3);
expect(runCount).toEqual(2);
expect(subRunCount).toEqual(3);
// b does not evaluate
expect(b()).toEqual(9);
expect(runCount).toEqual(2);
expect(subRunCount).toEqual(3);
expect(cancelSubscription2()).toEqual(0);
});
it('can be detached', () => { it('can be detached', () => {
const a = observable(2); const a = observable(2);
const b = computed(square, [a]); const b = computed(square, [a]);

View File

@ -44,12 +44,21 @@ describe('observable', () => {
runCount += 1; runCount += 1;
expect(val).toEqual(currentValue); expect(val).toEqual(currentValue);
}); });
const cancelSubscription2 = a.subscribe(val => {
runCount += 1;
expect(val).toEqual(currentValue);
});
expect(a(1)).toEqual(1); expect(a(1)).toEqual(1);
expect(runCount).toEqual(1); expect(runCount).toEqual(2);
expect(a(1)).toEqual(1); expect(a(1)).toEqual(1);
expect(runCount).toEqual(1); expect(runCount).toEqual(2);
cancelSubscription(); expect(cancelSubscription()).toEqual(1);
currentValue = 3;
expect(a(3)).toEqual(3); expect(a(3)).toEqual(3);
expect(runCount).toEqual(1); expect(runCount).toEqual(3);
expect(cancelSubscription2()).toEqual(0);
currentValue = 4;
expect(a(4)).toEqual(4);
expect(runCount).toEqual(3);
}); });
}); });

View File

@ -31,7 +31,10 @@ export function computed(fn, dependencies = []) {
accessor.subscribe = fn => { accessor.subscribe = fn => {
subscribers.add(fn); subscribers.add(fn);
return () => subscribers.delete(fn); return () => {
subscribers.delete(fn);
return subscribers.size;
};
}; };
accessor._d = fn => { accessor._d = fn => {

View File

@ -11,7 +11,10 @@ export function observable(store) {
accessor.subscribe = accessor._d = fn => { accessor.subscribe = accessor._d = fn => {
subscribers.add(fn); subscribers.add(fn);
return () => subscribers.delete(fn); return () => {
subscribers.delete(fn);
return subscribers.size;
};
}; };
accessor.unsubscribeAll = () => subscribers.clear(); accessor.unsubscribeAll = () => subscribers.clear();