Promises: the await block
Data from a server does not arrive at once: a request returns a promise that settles later. The {#await} block lets you describe all three states of a promise in the markup — waiting, success and error — without extra variables like isLoading.
Three branches
As an example, take the test service jsonplaceholder.typicode.com, which returns tasks as JSON:
<!-- src/routes/+page.svelte -->
<script>
async function loadTodos() {
const response = await fetch('https://jsonplaceholder.typicode.com/todos?_limit=5');
if (!response.ok) throw new Error('The server responded with an error');
return response.json();
}
let promise = $state(loadTodos());
</script>
{#await promise}
<p>Loading...</p>
{:then todos}
<ul>
{#each todos as todo (todo.id)}
<li>{todo.title}</li>
{/each}
</ul>
{:catch error}
<p>Could not load: {error.message}</p>
{/await}First "Loading..." flashes on the page, then a list of five tasks appears. If you turn off the internet and reload the page, you see the error message. The value in the {:then todos} branch is the result of the promise, in {:catch error} — the rejection reason.
Repeating the request
The block watches the variable holding the promise. Assign a new promise to it and the block goes from loading to the result again:
<!-- src/routes/+page.svelte: add a button below the await block -->
<button onclick={() => (promise = loadTodos())}>Refresh</button>After the click "Loading..." appears again, followed by a fresh list. This is why the promise is kept in $state: a plain variable would not tell the block about the replacement.
Short forms
If you do not need a loading indicator, write the then branch right in the opening tag:
<script>
let promise = $state(fetch('https://jsonplaceholder.typicode.com/todos/1').then((r) => r.json()));
</script>
{#await promise then todo}
<p>First task: {todo.title}</p>
{/await}Until the promise settles, nothing is shown in that place. Errors are not shown in this form — add if they matter.
Where to load data in SvelteKit
The {#await} block is good for requests triggered by the user: search, a "Refresh" button. Data a page needs as soon as it opens is usually fetched in SvelteKit with a load function — it has its own lesson in the SvelteKit chapter.
Common mistake
Calling the loading function right in the markup:
<script>
async function loadTodos() {
const response = await fetch('https://jsonplaceholder.typicode.com/todos?_limit=5');
return response.json();
}
</script>
{#await loadTodos() then todos}
<p>Tasks: {todos.length}</p>
{/await}This code works, but the request cannot be repeated, and every time the block is recreated it is sent again. Keep the promise in a variable and pass that variable to {#await}.