FRPtools 1.2.0

- add the ability to have comparators for complex types
- computed.unsubscribeAll() to give more cleanup options
This commit is contained in:
Timothy Farrell 2017-11-02 20:58:01 -05:00
parent b6c220e61e
commit 24e65c824f
5 changed files with 106 additions and 2 deletions

View File

@ -46,6 +46,26 @@ 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(); const remainingSubscriptionCount = unsubscribe();
inViewport.unsubscribeAll(); // Call unsubscribeAll to remove child observables/computeds.
```
### Provide a comparator for complex types
When storing a type that is not determined to be equal with simple equality (===), provide a
function to determine in the new provided value should be propagated to dependents.
```js
function setEquals(a, b) {
return (
a instanceof Set &&
b instanceof Set &&
[...a].reduce((acc, d) => acc && b.has(d), true) &&
[...b].reduce((acc, d) => acc && a.has(d), true)
);
}
const a = observable(new Set([1, 2]), setEquals);
``` ```
# [computed](./src/computed.js) # [computed](./src/computed.js)
@ -111,6 +131,33 @@ showDialog.subscribe(console.log.bind(console));
inViewport(false); // showDialog result recomputed and `false` is written to the console. inViewport(false); // showDialog result recomputed and `false` is written to the console.
shouldShow(false); // showDialog result recomputed, console.log is not called. shouldShow(false); // showDialog result recomputed, console.log is not called.
showDialog(); // showDialog does not recompute, console.log is not called. `false` is returned. showDialog(); // showDialog does not recompute, console.log is not called. `false` is returned.
showDialog.detach(); // Call detach to remove this computed from the logic tree.
showDialog.unsubscribeAll(); // Call unsubscribeAll to remove child observables/computeds.
```
### Provide a comparator for complex types
When the computed result is a type that is not determined to be equal with simple equality (===),
provide a function to determine in the new provided value should be propagated to dependents.
```js
function setEquals(a, b) {
return (
a instanceof Set &&
b instanceof Set &&
[...a].reduce((acc, d) => acc && b.has(d), true) &&
[...b].reduce((acc, d) => acc && a.has(d), true)
);
}
function _intersection(a, b) {
return new Set([...a].filter(x => b.has(x)));
}
const a = observable(new Set([1, 2]), setEquals);
const b = observable(new Set([2, 3]), setEquals);
const intersection = computed(_intersection, [a, b], setEquals);
``` ```
# [bundle](./src/bundle.js) # [bundle](./src/bundle.js)

View File

@ -1,6 +1,6 @@
{ {
"name": "frptools", "name": "frptools",
"version": "1.1.0", "version": "1.2.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

@ -155,4 +155,37 @@ describe('computed', () => {
expect(b()).toEqual(4); expect(b()).toEqual(4);
expect(c()).toEqual(7); expect(c()).toEqual(7);
}); });
it('uses a comparator', () => {
function setEquals(a, b) {
return (
a instanceof Set &&
b instanceof Set &&
[...a].reduce((acc, d) => acc && b.has(d), true) &&
[...b].reduce((acc, d) => acc && a.has(d), true)
);
}
let runCount = 0;
function intersection(a, b) {
runCount += 1;
return new Set([...a].filter(x => b.has(x)));
}
const a = observable(new Set([1, 2]), setEquals);
const b = observable(new Set([2, 3]), setEquals);
const ABintersection = computed(intersection, [a, b], setEquals);
expect(runCount).toEqual(0);
expect([...ABintersection()]).toEqual([2]);
expect(runCount).toEqual(1);
b(new Set([3, 2]));
expect([...ABintersection()]).toEqual([2]);
expect(runCount).toEqual(1);
b(new Set([3, 2, 1]));
expect(runCount).toEqual(1);
expect([...ABintersection()]).toEqual([1, 2]);
expect(runCount).toEqual(2);
});
}); });

View File

@ -61,4 +61,26 @@ describe('observable', () => {
expect(a(4)).toEqual(4); expect(a(4)).toEqual(4);
expect(runCount).toEqual(3); expect(runCount).toEqual(3);
}); });
it('uses a comparator', () => {
function setEquals(a, b) {
return (
a instanceof Set &&
b instanceof Set &&
[...a].reduce((acc, d) => acc && b.has(d), true) &&
[...b].reduce((acc, d) => acc && a.has(d), true)
);
}
let runCount = 0;
const a = observable(new Set([1, 2]), setEquals);
a.subscribe(() => (runCount += 1));
expect([...a()]).toEqual([1, 2]);
expect(runCount).toEqual(0);
expect([...a(new Set([2, 1]))]).toEqual([1, 2]);
expect(runCount).toEqual(0);
expect([...a(new Set([3, 2, 1]))]).toEqual([3, 2, 1]);
expect(runCount).toEqual(1);
});
}); });

View File

@ -1,3 +1,5 @@
import { eq } from './util.js';
export function computed(fn, dependencies = [], comparator = eq) { export function computed(fn, dependencies = [], comparator = eq) {
const subscribers = new Set(); const subscribers = new Set();
const dependents = new Set(); const dependents = new Set();
@ -23,7 +25,7 @@ export function computed(fn, dependencies = [], comparator = eq) {
if (isDirty) { if (isDirty) {
const newVal = fn.apply(null, dependencies.map(runParam)); const newVal = fn.apply(null, dependencies.map(runParam));
isDirty = false; isDirty = false;
if (!comparator(newVal, val)) { if (!comparator(val, newVal)) {
val = newVal; val = newVal;
subscribers.forEach(s => s(val)); subscribers.forEach(s => s(val));
} }