Skip to content

Instantly share code, notes, and snippets.

@jonasgeiler
Last active December 17, 2023 16:40
Show Gist options
  • Save jonasgeiler/07cd76d47dc760f52aab0ab3f7ce7151 to your computer and use it in GitHub Desktop.
Save jonasgeiler/07cd76d47dc760f52aab0ab3f7ce7151 to your computer and use it in GitHub Desktop.
A simple "subscribeAll" function for Svelte Stores
import type { Readable, Unsubscriber } from 'svelte/store';
/** List of Readable stores */
type Stores = [ Readable<any>, ...Array<Readable<any>> ] | Array<Readable<any>>;
/** Values from a list of Readable stores */
type StoresValues<T> = {
[K in keyof T]: T[K] extends Readable<infer U> ? U : never;
};
/** Does nothing (no operation) */
function noop() {
}
/**
* Subscribe to multiple svelte stores at once.
* @param stores - List of stores to subscribe to.
* @param callback - Callback that is called with the store values each time one of stores was updated.
*/
export function subscribeAll<S extends Stores>(stores: S, callback: (values: StoresValues<S>) => Unsubscriber | void): Unsubscriber {
// Most of the logic was taken from the derived function in `svelte/stores`
let initiated = false; // This prevents sync before all stores where subscribed
let values = new Array(stores.length);
let pending = 0; // Binary with one bit for each store
let cleanup: Unsubscriber = noop; // Cleanup function
const sync = () => {
if (pending) return;
cleanup();
const result = callback(values as StoresValues<S>);
cleanup = typeof result == 'function' ? result : noop;
};
const unsubscribeFunctions = stores.map((store, i) => store.subscribe(
value => {
values[i] = value;
pending &= ~(1 << i);
if (initiated) {
sync();
}
},
() => {
pending |= (1 << i);
},
));
initiated = true;
sync();
return function unsubscribeAll() {
for (let unsubscribe of unsubscribeFunctions) {
unsubscribe();
}
cleanup();
};
}
import { writable } from 'svelte/store';
import { subscribeAll } from './subscribeAll';
test('subscribeAll', () => {
const testStore1 = writable('Hello');
const testStore2 = writable('World');
const subscriber = jest.fn();
const unsubscribeAll = subscribeAll([ testStore1, testStore2 ], subscriber);
expect(subscriber).toHaveBeenCalledTimes(1);
expect(subscriber).toHaveBeenCalledWith([ 'Hello', 'World' ]);
testStore1.set('Hey');
expect(subscriber).toHaveBeenCalledTimes(2);
expect(subscriber).toHaveBeenCalledWith([ 'Hey', 'World' ]);
testStore2.set('There');
expect(subscriber).toHaveBeenCalledTimes(3);
expect(subscriber).toHaveBeenCalledWith([ 'Hey', 'There' ]);
unsubscribeAll(); // Unsubscribe from all stores
testStore1.set('Hi');
expect(subscriber).toHaveBeenCalledTimes(3); // Shouldn't be called again after unsubscribe
testStore2.set('JavaScript');
expect(subscriber).toHaveBeenCalledTimes(3); // Shouldn't be called again after unsubscribe
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment