Async data and {#await}

Svelte · Practical · 11 min

Most components display data from an API; the await block handles the three states declaratively.

The `{#await}` block renders a promise's three states:

{#await promise}
  <p>Loading...</p>
{:then user}
  <p>{user.name}</p>
{:catch error}
  <p>Failed: {error.message}</p>
{/await}

Short forms exist: {#await promise then value} skips the loading branch, and {:catch} can be omitted.

A common pattern stores the promise in state, or derives it from other state, so it reloads automatically:

let id = $state(1);
let userPromise = $derived(fetch(`/api/users/${id}`).then((r) => r.json()));

When id changes a new promise is created and the block shows the pending branch again. The block only renders the result of the latest promise, so a slow older response can't overwrite a newer one.

Keep in mind:

  • fetch only rejects on network errors; check res.ok and throw for HTTP errors so {:catch} runs.
  • During server-side rendering only the pending branch is rendered for unresolved promises; in SvelteKit, load data in load functions instead so pages render with data on the server.
  • For mutations (POST), call fetch in event handlers, not in derived values.

Svelte 5.36+ also has an experimental mode allowing await directly in markup and deriveds, covered in the expert lessons.

Example

<script>
  let id = $state(1);

  async function getUser(id) {
    const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
    if (!res.ok) throw new Error(`HTTP ${res.status}`);   // make 404 reject
    return res.json();
  }

  let user = $derived(getUser(id));        // new promise whenever id changes
</script>

<button onclick={() => id--} disabled={id === 1}>prev</button>
<button onclick={() => id++}>next</button>

{#await user}
  <p>Loading user {id}...</p>
{:then u}
  <h2>{u.name}</h2><p>{u.email}</p>
{:catch err}
  <p class="error">Could not load: {err.message}</p>
{/await}

Remember

  • {#await p}...{:then v}...{:catch e}{/await} handles loading, success and error
  • Derive the promise from state to refetch when inputs change
  • Throw on non-OK responses so the catch branch runs

Watch out for

  • Assuming fetch rejects on HTTP 404/500; it resolves, so check res.ok.

Check yourself

  1. The user clicks next quickly twice; the first request is slow and resolves last. What does {#await} show?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: svelte.dev/docs/svelte/await