Svelte logo

Svelte by Example: Async UI

An await block shows different markup while a promise is pending, after it resolves, or if it rejects.

In this example, we'll load saved todos from the browser's local storage.

Our small async function waits briefly to make loading visible, then reads saved todos. Invalid saved JSON will reject the promise.

Loading only starts when the button is clicked in the browser. We don't access local storage during server rendering.

The pending branch shows while we wait. :then receives the result, and :catch receives a rejected error.

TodoList.svelte
<script>
  let request = $state(null);

  async function loadTodos() {
    await new Promise((resolve) => setTimeout(resolve, 400));
    const todos = JSON.parse(localStorage.getItem('todos') ?? '[]');
    if (!Array.isArray(todos) || !todos.every((task) => typeof task === 'string')) {
      throw new Error('Saved todos must be a list of task names.');
    }
    return todos;
  }

  function load() {
    request = loadTodos();
  }
</script>

<button onclick={load}>Load saved todos</button>

{#if request}
  {#await request}
    <p>Loading todos…</p>
  {:then todos}
    {#each todos as task, index (index)}
      <p>{task}</p>
    {:else}
      <p>No saved todos yet.</p>
    {/each}
  {:catch error}
    <p>Couldn't load todos: {error.message}</p>
  {/await}
{/if}

To try a successful load, save a list of task names in the browser console. Save invalid JSON to try the error branch, then click the load button again to retry.

During server rendering, an await block with a promise renders only its pending branch. Our example renders just the button on the server, because loading hasn't started yet.

localStorage.setItem(
  'todos',
  JSON.stringify(['Walk dog']),
);