You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Svelte:如何在组件中处理自定义Writable Store异步init方法的Promise?

这问题其实挺典型的,核心点在于别把store的方法和自动订阅的store值混在一起,而且init只需要执行一次就行,没必要每次组件订阅都跑一遍。我先给你修复下原代码里的小bug,再给两个可行的解决方案:

首先,你当前的createRestaurantsStore里没解构set方法,init里调用的set会直接报错,得先补上:

const { subscribe, update, set } = writable({ collection: [] })

方案一:让Store管理加载/错误状态(推荐)

把加载中、错误信息这些状态也放进store里,这样所有组件都能通过自动订阅感知状态变化,还能保证init只执行一次。

修改后的Restaurant.js:

import { writable } from 'svelte/store'
export function createRestaurantsStore() {
  // 初始状态包含数据集合、加载状态、错误信息
  const { subscribe, update, set } = writable({
    collection: [],
    loading: false,
    error: null
  })

  let hasInitialized = false // 标记是否已经完成初始化,避免重复调用init

  return {
    subscribe,
    init: async () => {
      if (hasInitialized) return 
      hasInitialized = true

      // 更新加载状态
      set(state => ({ ...state, loading: true, error: null }))
      try {
        const response = await fetch('http://localhost:1337/restaurants')
        if(!response.ok) throw new Error(response.statusText)
        
        const json_response = await response.json()
        // 初始化完成,更新store状态
        set({
          collection: json_response,
          loading: false,
          error: null
        })
        return json_response
      } catch (err) {
        // 捕获错误并更新状态
        set(state => ({ ...state, loading: false, error: err.message }))
        throw err // 继续抛出错误,方便外部额外处理
      }
    },
    insert: async (restaurant) => {
      // 原逻辑不变,也可以加入类似的loading/error处理逻辑
      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 store_state
        })
        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 store_state
        })
        return json_response
      }
      throw Error(response.statusText)
    }
  }
}
export const restaurant_store = createRestaurantsStore()

对应的App.svelte可以通过自动订阅直接获取状态,处理加载、错误和数据展示:

<script>
  import { restaurant_store } from './Restaurant.js'
  import { onMount } from 'svelte'

  // 组件挂载时调用一次init即可
  onMount(async () => {
    try {
      await restaurant_store.init()
    } catch (err) {
      // 这里可以做额外的错误处理,比如上报日志
      console.error('初始化餐厅数据失败:', err)
    }
  })
</script>

<main>
  {#if $restaurant_store.loading}
    <p>正在加载餐厅数据...</p>
  {:else if $restaurant_store.error}
    <p class="error">加载出错: {$restaurant_store.error}</p>
  {:else}
    <ul>
      {#each $restaurant_store.collection as restaurant}
        <li>{restaurant.name}</li>
      {/each}
    </ul>
  {/if}
</main>

<style>
  .error { color: #dc2626; }
</style>

方案二:组件内单独处理Init(不把状态放进Store)

如果你不想把加载/错误状态放进store,也可以在组件里用onMount调用init——因为onMount只会执行一次,刚好符合init只需要跑一遍的需求:

修改后的App.svelte:

<script>
  import { restaurant_store } from './Restaurant.js'
  import { onMount } from 'svelte'

  let loading = true
  let error = null

  onMount(async () => {
    try {
      await restaurant_store.init()
      loading = false
    } catch (err) {
      loading = false
      error = err.message
      console.error('初始化失败:', err)
    }
  })
</script>

<main>
  {#if loading}
    <p>正在加载...</p>
  {:else if error}
    <p class="error">出错了: {error}</p>
  {:else}
    <ul>
      {#each $restaurant_store.collection as restaurant}
        <li>{restaurant.name}</li>
      {/each}
    </ul>
  {/if}
</main>

<style>
  .error { color: #dc2626; }
</style>

关键注意点

  • 永远不要在模板里直接写{await $restaurant_store.init()}:$restaurant_store是store的值,而init是store的方法,自动订阅的$前缀只能访问store的值,不能调用store的方法。
  • 一定要保证init只执行一次:不管是用store内部的标记,还是组件的onMount,都要避免重复请求API。

内容的提问来源于stack exchange,提问作者JarsOfJam-Scheduler

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 18:32:48