solve diamond dependencies (#2660)

pull/2955/head
Rich Harris 5 years ago
parent 04162b9e68
commit 3805421d44

@ -112,12 +112,12 @@ type StoresValues<T> = T extends Readable<infer U> ? U :
* applying an aggregation function over its input values. * applying an aggregation function over its input values.
* @param {Stores} stores input stores * @param {Stores} stores input stores
* @param {function(Stores=, function(*)=):*}fn function callback that aggregates the values * @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<T, S extends Stores>( export function derived<T, S extends Stores>(
stores: S, stores: S,
fn: (values: StoresValues<S>, set?: Subscriber<T>) => T | Unsubscriber | void, fn: (values: StoresValues<S>, set?: Subscriber<T>) => T | Unsubscriber | void,
initial_value?: T, value?: T,
): Readable<T> { ): Readable<T> {
const single = !Array.isArray(stores); const single = !Array.isArray(stores);
@ -127,12 +127,28 @@ export function derived<T, S extends Stores>(
const auto = fn.length < 2; const auto = fn.length < 2;
return readable(initial_value, (set) => { const subscribers: Array<SubscribeInvalidateTuple<T>> = [];
let inited = false; 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<S> = [] as StoresValues<S>; const values: StoresValues<S> = [] as StoresValues<S>;
let pending = 0; let pending = 0;
let cleanup = noop; running = false;
const sync = () => { const sync = () => {
if (pending) { if (pending) {
@ -147,27 +163,47 @@ export function derived<T, S extends Stores>(
} }
}; };
const unsubscribers = stores_array.map((store, i) => store.subscribe( unsubscribers = stores_array.map((store, i) => store.subscribe(
(value) => { (value) => {
values[i] = value; values[i] = value;
pending &= ~(1 << i); pending &= ~(1 << i);
if (inited) { if (running) {
sync(); sync();
} }
}, },
() => { () => {
invalidate();
pending |= (1 << i); pending |= (1 << i);
}), }),
); );
inited = true;
sync(); sync();
running = true;
}
return function stop() { function stop() {
run_all(unsubscribers); run_all(unsubscribers);
cleanup(); cleanup();
}; }
});
return {
subscribe(run: Subscriber<T>, invalidate: Invalidater<T> = noop): Unsubscriber {
const subscriber: SubscribeInvalidateTuple<T> = [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();
}
};
}
};
} }
/** /**

@ -189,6 +189,34 @@ describe('store', () => {
unsubscribe(); 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', () => { it('is updated with safe_not_equal logic', () => {
const arr = [0]; const arr = [0];

Loading…
Cancel
Save