异步初始化自定义 Svelte 存储

Initializing a custom Svelte store asynchronously

背景
我正在尝试使用 Svelte 和 Tauri
开发跨平台桌面应用程序 当应用程序启动时,我需要将文件系统中的 settings.json 文件加载到自定义 Svelte 商店中。
它需要是自定义存储,因为我必须在写入数据之前使用自定义设置函数验证数据
商店将持有一个对象。

我使用的是常规 Svelte 而不是 Svelte-kit,因为不需要 SSR。

问题

  1. Tauri 在他们的 fs-api
  2. 中没有任何同步读取文件的方法
  3. Svelte 似乎没有任何直观的方法可以做到这一点

测试

例子
如果我要 post 所有失败的尝试,那将是很多代码,所以我将提供一个我试图实现的示例。
当 createStore 不是异步时,代码中的所有内容都有效,除了读取设置文件。

import { writable, get as getStore } from 'svelte/store'; // Svelte store
import _set from 'lodash.set';                            // Creating objects with any key/path
import _merge from 'lodash.merge';                        // Merging objects
import { fs } from '@tauri-apps/api';                     // Accessing local filesystem


async function createStore() {
  // Read settings from the file system
  let settings = {}
  try { settings = JSON.parse(await fs.readTextFile('./settings.json')); }
  catch {}

  // Create the store
  const store = writable(settings);

  // Custom set function
  function set (key, value) {
    if(!key) return;

    // Use lodash to create an object
    const change = _set({}, key, value);

    // Retreive the current store and merge it with the object above
    const currentStore = getStore(store)
    const updated = _merge({}, currentStore, change)

    // Update the store
    store.update(() => updated)
    
    // Save the updated settings back to the filesystem
    fs.writeFile({
      contents: JSON.stringify(updated, null, 2),
      path: './settings.json'}
    )
  }

  // Bundle the custom store
  const customStore = {
    subscribe: store.subscribe,
    set
  }

  return customStore;
}

export default createStore();

当有一个需要异步初始化的自定义商店时,如果直接需要商店,我会通过商店上的异步方法执行此操作,我会从 App 组件调用该方法
(请注意 fs.writeFile() 也是 returns 一个 Promise。如果有错误,这还不会被处理...)

App.svelte
<script>
    import settings from './settings'
    import {onMount} from 'svelte'
    
    let appInitialized

    onMount(async () => {
        try {
            await settings.init()           
            appInitialized = true
        }catch(error) {
            console.error(error)
        }
    })

</script>

{#if appInitialized}
    'showing App'
{:else}
    'initializing App'
{/if}

只有一个商店要使用 {#await} block

进行初始化时的替代组件逻辑
<script>
    import settings from './settings'
</script>

{#await settings.init()}
    'initializing store'
{:then}
    'show App'
{:catch error}
    'Couldn't initialize - '{error.message}
{/await}

如果有更多商店要初始化,则为一个

<script>
    import settings from './settings'
    import store2 from './store2'
    import store3 from './store3'

    const initStores = [
        settings.init(),
        store2.init(),
        store3.init()
    ]
</script>

{#await Promise.all(initStores)}
    'initializing stores'
{:then}
    'showing App'
{:catch error}
    'Couldn't initialize - '{error.message}
{/await}
settings.js
import { writable, get } from 'svelte/store';
import { fs } from '@tauri-apps/api';  

function createStore() {

    let initialValue = {}
    // destructure the store on creation to have 'direct access' to methods
    const {subscribe, update, set} = writable(initialValue);

    return {
        subscribe,

        async init() {
            const savedSettings = JSON.parse(await fs.readTextFile('./settings.json'))
            set(savedSettings);
        },

        changeSetting(key, value) {
            if(!key) return;

            const storeValue = get(this)

            storeValue[key] = value

            update(_ => storeValue)
            
            fs.writeFile({
                contents: JSON.stringify(storeValue, null, 2),
                path: './settings.json'
            })
        }
    }
}

export default createStore();

更新:我建议在 Corrl 修订后的答案中使用包装器组件方法,
但是使用 #await 块而不是 #if.


由于加载设置是应用程序启动的一部分,您可以延迟加载 Svelte 应用程序,直到加载设置。

这允许组件使用商店而不用担心加载状态:

// main.js
initSettings().then(()=> {
    new App({ target: document.body })
})

// settings.js
import { writable, get } from 'svelte/store';
import { fs } from '@tauri-apps/api';  

let store;
const settings = { 
  subscribe() {
    if (!store) {
      throw new Error('Not initialized')
    }
    return store.subscribe()
  },
  async changeSetting(key, value) {
    if (!store) {
      throw new Error('Not initialized')
    }
    // ... save to fs
  }
}

export default settings;

export async function initSettings() {
  const data = JSON.parse(await fs.readTextFile('./settings.json'))
  if (store) {
    store.set(data)
  } else {
    store = writable(data);
  }
}

缺点是它会延迟应用程序的启动,如果您不在 main.js 中实现 .catch,当承诺被拒绝时,应用程序将保持空白。