From a2ff93cb721b786f34e467b9bddfbf6eebcfde43 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 16 Dec 2018 19:49:42 -0500 Subject: [PATCH] glitch-free reactive stores --- store.js | 46 +++++++++++++++++++++++++++++---------------- test/store/index.js | 22 ++++++++++++++++++++++ 2 files changed, 52 insertions(+), 16 deletions(-) diff --git a/store.js b/store.js index ffef5c7ca8..d22afdedee 100644 --- a/store.js +++ b/store.js @@ -1,4 +1,4 @@ -import { run_all } from './internal.js'; +import { run_all, noop } from './internal.js'; export function readable(start, value) { const subscribers = []; @@ -7,20 +7,22 @@ export function readable(start, value) { function set(newValue) { if (newValue === value) return; value = newValue; - subscribers.forEach(fn => fn(value)); + subscribers.forEach(s => s[1]()); + subscribers.forEach(s => s[0](value)); } return { - subscribe(fn) { + subscribe(run, invalidate = noop) { if (subscribers.length === 0) { stop = start(set); } - subscribers.push(fn); - fn(value); + const subscriber = [run, invalidate]; + subscribers.push(subscriber); + run(value); return function() { - const index = subscribers.indexOf(fn); + const index = subscribers.indexOf(subscriber); if (index !== -1) subscribers.splice(index, 1); if (subscribers.length === 0) { @@ -38,19 +40,21 @@ export function writable(value) { function set(newValue) { if (newValue === value) return; value = newValue; - subscribers.forEach(fn => fn(value)); + subscribers.forEach(s => s[1]()); + subscribers.forEach(s => s[0](value)); } function update(fn) { set(fn(value)); } - function subscribe(fn) { - subscribers.push(fn); - fn(value); + function subscribe(run, invalidate = noop) { + const subscriber = [run, invalidate]; + subscribers.push(subscriber); + run(value); return () => { - const index = subscribers.indexOf(fn); + const index = subscribers.indexOf(subscriber); if (index !== -1) subscribers.splice(index, 1); }; } @@ -63,20 +67,30 @@ export function derive(stores, fn) { if (single) stores = [stores]; const auto = fn.length === 1; + let value = {}; return readable(set => { let inited = false; const values = []; + let pending = 0; + const sync = () => { + if (pending) return; const result = fn(single ? values[0] : values, set); - if (auto) set(result); + if (auto && (value !== (value = result))) set(result); } - const unsubscribers = stores.map((store, i) => store.subscribe(value => { - values[i] = value; - if (inited) sync(); - })); + const unsubscribers = stores.map((store, i) => store.subscribe( + value => { + values[i] = value; + pending &= ~(1 << i); + if (inited) sync(); + }, + () => { + pending |= (1 << i); + }) + ); inited = true; sync(); diff --git a/test/store/index.js b/test/store/index.js index 355cea4442..fb518cf24d 100644 --- a/test/store/index.js +++ b/test/store/index.js @@ -128,5 +128,27 @@ describe('store', () => { number.set(7); assert.deepEqual(values, [0, 2, 4]); }); + + it('prevents glitches', () => { + const lastname = writable('Jekyll'); + const firstname = derive(lastname, n => n === 'Jekyll' ? 'Henry' : 'Edward'); + + const fullname = derive([firstname, lastname], names => names.join(' ')); + + const values = []; + + const unsubscribe = fullname.subscribe(value => { + values.push(value); + }); + + lastname.set('Hyde'); + + assert.deepEqual(values, [ + 'Henry Jekyll', + 'Edward Hyde' + ]); + + unsubscribe(); + }); }); });