Promises lassen sich in Svelte direkt im Template behandeln — kein useEffect-Konstrukt, keine separate State-Variable für Loading/Error. Der {#await}-Block deckt mit {:then} (Erfolg) und {:catch} (Fehler) alle drei Zustände ab. Dieser Artikel zeigt die Varianten, die Kombination mit fetch und das Verhältnis zu SvelteKit-load-Funktionen.

Grundsyntax

svelte Vollständiger await-Block
<script>
    let promise = fetch('/api/users/1').then((r) => r.json());
</script>

{#await promise}
    <p>Lädt …</p>
{:then user}
    <p>Hallo, {user.name}</p>
{:catch error}
    <p style="color: red">Fehler: {error.message}</p>
{/await}

Drei Zweige:

  • {#await promise} – Loading-Zustand, solange das Promise pending ist.
  • {:then result} – Erfolg, der Wert ist im Block verfügbar.
  • {:catch error} – Fehler, der Error-Wert ist im Block verfügbar.

Kurzform ohne Loading

Wenn kein Loading-State angezeigt werden soll (z. B. weil Server-Side-Rendering die Daten schon liefert), reicht eine Kurzform:

svelte Ohne Loading-Block
{#await promise then user}
    <p>Hallo, {user.name}</p>
{/await}

Während des Pending-Zustands wird hier gar nichts gerendert.

Nur Loading + Erfolg ohne Catch

Wenn catch an anderer Stelle behandelt wird (z. B. Error Boundary), kann er weggelassen werden:

svelte Ohne Catch
{#await promise}
    <Spinner />
{:then user}
    <UserCard {user} />
{/await}

Bei einer Fehler im Promise wird die nächste umschließende <svelte:boundary>-Komponente ausgelöst.

Reaktivität bei Promise-Wechsel

Wenn die Promise-Variable wechselt (z. B. weil sich eine userId ändert), reagiert der Block automatisch — neuer Loading-Zustand, neue Auflösung:

svelte Wechsel des Promises
<script>
    let userId = $state(1);
    let promise = $derived(
        fetch(`/api/users/${userId}`).then((r) => r.json())
    );
</script>

<select bind:value={userId}>
    <option value={1}>User 1</option>
    <option value={2}>User 2</option>
    <option value={3}>User 3</option>
</select>

{#await promise}
    <p>Lädt …</p>
{:then user}
    <p>{user.name}</p>
{:catch error}
    <p>Fehler: {error.message}</p>
{/await}

Beim Auswählen einer anderen ID wird ein neues Promise erstellt — der Block springt automatisch zurück in den Loading-Zustand.

Verwendung mit async function im Script

Statt das Promise inline zu erzeugen, oft besser eine Funktion definieren:

svelte Mit Funktions-Wrap
<script>
    let userId = $state(1);

    async function loadUser(id) {
        const res = await fetch(`/api/users/${id}`);
        if (!res.ok) throw new Error('Nicht gefunden');
        return res.json();
    }

    let promise = $derived(loadUser(userId));
</script>

{#await promise}
    <Spinner />
{:then user}
    <UserCard {user} />
{:catch error}
    <ErrorBanner {error} />
{/await}

Vorteil: Der Code ist testbar und wiederverwendbar.

TypeScript: typed values

{:then result} erbt den Promise-Typ — IDE und Compiler kennen result.foo korrekt:

svelte TypeScript-Beispiel
<script lang="ts">
    type User = { id: number; name: string; email: string };

    async function loadUser(id: number): Promise<User> {
        const res = await fetch(`/api/users/${id}`);
        return res.json();
    }

    let userId = $state(1);
    let promise = $derived(loadUser(userId));
</script>

{#await promise}
    <p>Lädt …</p>
{:then user}
    <p>{user.email}</p> <!-- user: User -->
{:catch error}
    <p>{(error as Error).message}</p>
{/await}

{#await} vs. SvelteKit-load

In SvelteKit gibt es einen anderen Weg, Daten zu laden: die load-Funktion. Sie läuft auf dem Server (oder im Browser) bevor die Seite gerendert wird, und liefert Daten als data-Prop.

Aspekt{#await}SvelteKit-load
Wann läuft?Während des Renderns (Client)Vor dem Rendering (SSR & CSR)
Loading-AnzeigeEingebaut ({#await})Manuell oder via streamed-Pattern
SEODaten erst nach Hydration sichtbarDaten Teil des SSR-HTML
TypeScriptInferred aus PromiseGeneriert via $types
Geeignet fürLazy/optionale Daten, DrittsystemePage-Daten, Authentifizierung, SEO

Faustregel: Page-Daten gehören in load, optionale oder lazy Daten in {#await}.

Mehr zur load-Funktion im Artikel SvelteKit Routing & Loading.

Praxis-Beispiel: User-Profil mit Retry

svelte UserProfile.svelte
<script>
    let { userId } = $props();
    let attempt = $state(0);

    async function loadUser(id) {
        const res = await fetch(`/api/users/${id}`);
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        return res.json();
    }

    let promise = $derived(loadUser(userId));
    // Re-Trigger bei Klick auf Retry
    $effect(() => { attempt; });
</script>

{#await promise}
    <Spinner />
{:then user}
    <article>
        <h1>{user.name}</h1>
        <p>{user.email}</p>
    </article>
{:catch error}
    <div class="error">
        <p>Fehler: {error.message}</p>
        <button onclick={() => attempt++}>Erneut versuchen</button>
    </div>
{/await}

Anmerkung: Für ein „echtes Retry” reicht das Erhöhen einer Zähler-Variable nicht aus, wenn loadUser rein vom userId abhängt. Hier: userId und attempt beide in loadUser einbeziehen oder mit dedizierter Wrapper-Logik arbeiten.

Häufige Stolperfallen

Promise im Render-Body neu erzeugt.

svelte − Endlos-Loop
{#await fetch('/api/data').then((r) => r.json())}
    <Spinner />
{/await}

Bei jedem Re-Render wird ein neues Promise erzeugt — Block bleibt im Loading-State. Lieber das Promise im Script in einer Variable halten.

Race-Condition bei sich ändernder ID. Wenn die User-ID schnell wechselt, kann eine alte Promise-Resolution eine spätere überschreiben. In komplexeren Fällen mit AbortController oder einer Bibliothek wie TanStack Query absichern.

error als unknown. TypeScript typisiert catch-Werte als unknown (oder Error je nach Config). Ein expliziter Cast (error as Error).message oder eine instanceof-Prüfung sind oft nötig.

Errors verschluckt. Ohne {:catch} und ohne umschließende Boundary werden Promise-Errors als unhandled-Rejection geloggt, aber im UI taucht nichts auf. In Production zu vermeiden.

Weiterführende Ressourcen

Externe Quellen

Verwandte Artikel

/ Weiter

Zurück zu Template-Syntax

Zur Übersicht