Skip to content

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.

React
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>;
}
Svelte
<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
// 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
<!-- Svelte — no extra library, no boundary components -->
{#await userPromise}
<Spinner />
{:then user}
<UserCard {user} />
{:catch err}
<ErrorUI message={err.message} />
{/await}

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.

What is the correct syntax for the three sections of an {#await} block?
What happens in Svelte when you assign a new Promise to the variable used in {#await}?
How is {#await} most similar to React's Suspense approach?
You want to skip showing a loading state and only show the resolved value. Which shorthand works?