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
相关产品推荐
相关产品推荐

