Search code examples
sveltesvelte-3svelte-componentsvelte-store

Svelte: How to handle the custom writable store's async init's promise in the component?


I have several Svelte components and a custom writable store. The store has an init function which is async and which fills the store's value with some REST API's db's table's data. My components must all subscribe to this store by using autosubscription. At subscription, init must be called. The global idea is to implement CRUD operations on the db with CRUD operations on the store (useful to show the store's value, i.e. the db's table, with reactivity).

As init is async and, thus, returns a promise, I need to await it in my components. But since I use autosubscription (by prefixing the store name with $), how can I do that?

For example: App.svelte (the component):

<script>
    import { restaurant_store } from './Restaurant.js'
    export let name
</script>
    
<main>
    <!--- I need to handle the promise and rejection here -->
    {#each $restaurant_store as restaurant}
    <li>
        {restaurant.name}
    </li>
    {/each}
</main>

Restaurant.js (the store):

import { writable } from 'svelte/store'
    
export function createRestaurantsStore() {
    const { subscribe, update } = writable({ collection: [] })
    
    return {
        subscribe,
        init: async () => {
            const response = await fetch('http://localhost:1337/restaurants')
    
            if(response.ok) {
                const json_response = await response.json()
                set({ collection: json_response })
                return json_response
            }
            throw Error(response.statusText)
        },
        insert: async (restaurant) => {
            const response = await fetch('http://localhost:1337/restaurants', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(restaurant)
            })
    
            if(response.ok) {
                const json_response = await response.json()
                update(store_state => store_state.collection.push(json_response))
                return json_response
            }
            throw Error(response.statusText)
        },
    
        update: async (restaurant, id) => {
            const response = await fetch('http://localhost:1337/restaurants/' + id, {
                method: 'PUT',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(restaurant)
            })
    
            if(response.ok) {
                const json_response = await response.json()
                update(store_state => {
                    const current_id = store_state.collection.findIndex(e => e.id === id)
                    store_state.collection.splice(current_id, 1, json_response)
                })
                return json_response
            }
            throw Error(response.statusText)
        }
    }
}
    
export const restaurant_store = createRestaurantsStore()

Solution

  • Svelte provides a built-in mechanism to handle the possible states of promises within your template, so the same could look like this:

    <main>
        {#await restaurant_store.init()}
            <p>waiting for the promise to resolve...</p>
        {:then}
          {#each $restaurant_store as restaurant}
            <li>
              {restaurant.name}
            </li>
          {/each}
        {:catch error}
            <p>Something went wrong: {error.message}</p>
        {/await}
    </main>
    

    Checkout the REPL for a live example.