Search code examples
sveltesvelte-3svelte-store

how does svelte unsubscribe actually work?


I can subscribe to a store like this:

count.subscribe(value => {
  count_value = value;
});

but when we want to unsubscribe we will put the previous subscribe code into a new variable (becomes a function expression) and run it only when the component is destroyed (onDestroy)

const unsubscribe = count.subscribe(value => {
    count_value = value;
});

onDestroy(unsubscribe);

the question is, how about just putting the previous function into a new variable called unsubscribe. can perform the unsubscribe function to the store. I mean we don't even change the subscribe code at all for the unsubscribe implementation, all we do is put it in a new variable so it becomes a function expression and only call it via onDestroy, then how can it magically unsubscribe? how does it actually work?


Solution

  • subscribe does not return the function you passed as a parameter: it returns a new function that implements the unsubscribe behavior.


    This is easier to understand if you look at the Svelte documentation for custom store contracts (emphasis added):

    Store contract

    store = { subscribe: (subscription: (value: any) => void) => (() => void), set?: (value: any) => void }
    

    You can create your own stores without relying on svelte/store, by implementing the store contract:

    1. A store must contain a .subscribe method, which must accept as its argument a subscription function. This subscription function must be immediately and synchronously called with the store's current value upon calling .subscribe. All of a store's active subscription functions must later be synchronously called whenever the store's value changes.
    2. The .subscribe method must return an unsubscribe function. Calling an unsubscribe function must stop its subscription, and its corresponding subscription function must not be called again by the store.

    If you want further proof that the return value of subscribe is different than the function you passed it, just extract the type for store.subscribe:

    Type subscribe = (subscription: (value: any) => void) => (() => void)
    

    The signature of the subscription parameter, (value: any) => void, does not match the signature of the return value, () => void.


    Here is a simple implementation of the store contract demonstrating how the unsubscribe function is built.

    class Store {
        constructor(init) {
            this.subscribers = {};
            this.value = init;
        }
        subscribe(callback) {
            callback(this.value);
            const id = Symbol()
            this.subscribers[id] = callback;
    
            return () => delete this.subscribers[id]
            //     ^^^ unsubscribe function here ^^^
    
        }
        set(value) {
            this.value = value;
            for (const id of Object.getOwnPropertySymbols(this.subscribers)) {
                this.subscribers[id](value)
            }
        }
    }