Async Patterns
React developers track async state manually: three useState calls for loading, data, and error, then an if/else chain in JSX. React 18 introduced <Suspense> and error boundaries to make this declarative, but they require additional setup and a data library that throws Promises.
Svelte has a single, elegant answer baked into the template syntax:
{#await promise} <!-- loading -->{:then value} <!-- resolved -->{:catch err} <!-- rejected -->{/await}One block. Three states. No hooks, no error boundary components, no thrown Promises. If the promise variable is replaced with a new Promise (for example, when the user clicks “Reload”), Svelte automatically re-enters the loading state.
Manual useState vs {#await}
Section titled “Manual useState vs {#await}”function UserCard({ userId }) { const [user, setUser] = React.useState(null); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(null);
React.useEffect(() => { setLoading(true); fetch(`/api/users/${userId}`) .then(r => r.json()) .then(data => { setUser(data); setLoading(false); }) .catch(err => { setError(err.message); setLoading(false); }); }, [userId]);
if (loading) return <p>Loading...</p>; if (error) return <p style={{ color: 'red' }}>Error: {error}</p>; return <div><h2>{user.name}</h2><p>{user.email}</p></div>;}<script> let { userId } = $props();
async function loadUser(id) { const res = await fetch(`/api/users/${id}`); if (!res.ok) throw new Error('Not found'); return res.json(); }
let userPromise = $derived(loadUser(userId));</script>
{#await userPromise} <p>Loading...</p>{:then user} <div> <h2>{user.name}</h2> <p>{user.email}</p> </div>{:catch err} <p style="color:red">Error: {err.message}</p>{/await}When userId changes, $derived creates a new Promise, and {#await} immediately re-enters the pending state. No manual state resets needed.
React Suspense reference vs {#await}
Section titled “React Suspense reference vs {#await}”// React — requires library support (react-query, SWR, Relay)// The library must throw a Promise for Suspense to catch<Suspense fallback={<Spinner />}> <ErrorBoundary fallback={<ErrorUI />}> <UserCard userId={1} /> </ErrorBoundary></Suspense><!-- Svelte — no extra library, no boundary components -->{#await userPromise} <Spinner />{:then user} <UserCard {user} />{:catch err} <ErrorUI message={err.message} />{/await}Try it: simulated async with reload
Section titled “Try it: simulated async with reload”This demo uses setTimeout to simulate a 500 ms network call that occasionally rejects. Click “Load” to trigger a new Promise — {#await} handles all three states automatically.