Last active
December 17, 2023 16:40
-
-
Save jonasgeiler/07cd76d47dc760f52aab0ab3f7ce7151 to your computer and use it in GitHub Desktop.
A simple "subscribeAll" function for Svelte Stores
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
}; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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