From c386545cb6416e26d8095e2694f7e500189ce4f2 Mon Sep 17 00:00:00 2001 From: Timothy Farrell Date: Thu, 2 Nov 2017 20:58:01 -0500 Subject: [PATCH] FRPtools 1.2.0 - add the ability to have comparators for complex types - computed.unsubscribeAll() to give more cleanup options --- packages/frptools/README.md | 47 +++++++++++++++++++++++ packages/frptools/package.json | 2 +- packages/frptools/spec/computed.spec.js | 33 ++++++++++++++++ packages/frptools/spec/observable.spec.js | 22 +++++++++++ packages/frptools/src/computed.js | 4 +- 5 files changed, 106 insertions(+), 2 deletions(-) diff --git a/packages/frptools/README.md b/packages/frptools/README.md index fa78c27..e7ae958 100644 --- a/packages/frptools/README.md +++ b/packages/frptools/README.md @@ -46,6 +46,26 @@ it will provide the count of remaining subscriptions. ```js const unsubscribe = inViewport.subscribe(console.log.bind(console)); 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) @@ -111,6 +131,33 @@ showDialog.subscribe(console.log.bind(console)); inViewport(false); // showDialog result recomputed and `false` is written to the console. shouldShow(false); // showDialog result recomputed, console.log is not called. 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) diff --git a/packages/frptools/package.json b/packages/frptools/package.json index 11cedaf..d527a1a 100644 --- a/packages/frptools/package.json +++ b/packages/frptools/package.json @@ -1,6 +1,6 @@ { "name": "frptools", - "version": "1.1.0", + "version": "1.2.0", "description": "Observable and Computed data streams", "main": "lib/index.js", "jsnext:main": "src/index.js", diff --git a/packages/frptools/spec/computed.spec.js b/packages/frptools/spec/computed.spec.js index 7d909aa..d6ff30e 100644 --- a/packages/frptools/spec/computed.spec.js +++ b/packages/frptools/spec/computed.spec.js @@ -155,4 +155,37 @@ describe('computed', () => { expect(b()).toEqual(4); 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); + }); }); diff --git a/packages/frptools/spec/observable.spec.js b/packages/frptools/spec/observable.spec.js index 78d6bae..fa1c8cc 100644 --- a/packages/frptools/spec/observable.spec.js +++ b/packages/frptools/spec/observable.spec.js @@ -61,4 +61,26 @@ describe('observable', () => { expect(a(4)).toEqual(4); 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); + }); }); diff --git a/packages/frptools/src/computed.js b/packages/frptools/src/computed.js index 679c2c9..b622652 100644 --- a/packages/frptools/src/computed.js +++ b/packages/frptools/src/computed.js @@ -1,3 +1,5 @@ +import { eq } from './util.js'; + export function computed(fn, dependencies = [], comparator = eq) { const subscribers = new Set(); const dependents = new Set(); @@ -23,7 +25,7 @@ export function computed(fn, dependencies = [], comparator = eq) { if (isDirty) { const newVal = fn.apply(null, dependencies.map(runParam)); isDirty = false; - if (!comparator(newVal, val)) { + if (!comparator(val, newVal)) { val = newVal; subscribers.forEach(s => s(val)); }