From 3805421d44d64e995a5cb1dddc4cc653cb61ddc5 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 4 Jun 2019 14:05:41 -0400 Subject: [PATCH] solve diamond dependencies (#2660) --- src/runtime/store/index.ts | 62 ++++++++++++++++++++++++++++++-------- test/store/index.ts | 28 +++++++++++++++++ 2 files changed, 77 insertions(+), 13 deletions(-) diff --git a/src/runtime/store/index.ts b/src/runtime/store/index.ts index e7db228401..f03a550c5f 100644 --- a/src/runtime/store/index.ts +++ b/src/runtime/store/index.ts @@ -112,12 +112,12 @@ type StoresValues = T extends Readable ? U : * applying an aggregation function over its input values. * @param {Stores} stores input stores * @param {function(Stores=, function(*)=):*}fn function callback that aggregates the values - * @param {*=}initial_value when used asynchronously + * @param {*=}value initial value, when used asynchronously */ export function derived( stores: S, fn: (values: StoresValues, set?: Subscriber) => T | Unsubscriber | void, - initial_value?: T, + value?: T, ): Readable { const single = !Array.isArray(stores); @@ -127,12 +127,28 @@ export function derived( const auto = fn.length < 2; - return readable(initial_value, (set) => { - let inited = false; + const subscribers: Array> = []; + let unsubscribers; + let cleanup = noop; + let running = false; + + function invalidate() { + subscribers.forEach(subscriber => subscriber[1]()); + } + + function set(current_value) { + value = current_value; + if (running) { + invalidate(); + subscribers.forEach(subscriber => subscriber[0](value)); + } + } + + function start() { const values: StoresValues = [] as StoresValues; let pending = 0; - let cleanup = noop; + running = false; const sync = () => { if (pending) { @@ -147,27 +163,47 @@ export function derived( } }; - const unsubscribers = stores_array.map((store, i) => store.subscribe( + unsubscribers = stores_array.map((store, i) => store.subscribe( (value) => { values[i] = value; pending &= ~(1 << i); - if (inited) { + if (running) { sync(); } }, () => { + invalidate(); pending |= (1 << i); }), ); - inited = true; sync(); + running = true; + } - return function stop() { - run_all(unsubscribers); - cleanup(); - }; - }); + function stop() { + run_all(unsubscribers); + cleanup(); + } + + return { + subscribe(run: Subscriber, invalidate: Invalidater = noop): Unsubscriber { + const subscriber: SubscribeInvalidateTuple = [run, invalidate]; + subscribers.push(subscriber); + if (subscribers.length === 1) start(); + run(value); + + return () => { + const index = subscribers.indexOf(subscriber); + if (index !== -1) { + subscribers.splice(index, 1); + } + if (subscribers.length === 0) { + stop(); + } + }; + } + }; } /** diff --git a/test/store/index.ts b/test/store/index.ts index 13b8e1f869..77ef9f9549 100644 --- a/test/store/index.ts +++ b/test/store/index.ts @@ -189,6 +189,34 @@ describe('store', () => { unsubscribe(); }); + it('prevents diamond dependency problem', () => { + const count = writable(0); + const values = []; + + const a = derived(count, $count => { + return 'a' + $count; + }); + + const b = derived(count, $count => { + return 'b' + $count; + }); + + const combined = derived([a, b], ([a, b]) => { + return a + b; + }); + + const unsubscribe = combined.subscribe(v => { + values.push(v); + }); + + assert.deepEqual(values, ['a0b0']); + + count.set(1); + assert.deepEqual(values, ['a0b0', 'a1b1']); + + unsubscribe(); + }); + it('is updated with safe_not_equal logic', () => { const arr = [0];