ZyVOP Logo
Content That Connects
SeriesAI NewsWhy ZyVOPJoin Discord
LoginGet Started
ZyVOP Logo
Content That Connects

The Developer Publishing Hub. Write once, cross-post to Dev.to, Medium, Hashnode, WordPress & Bluesky with automated canonical source tags and zero paywalls.

Content

  • Categories
  • Tags
  • Badges
  • Leaderboard
  • Write Article
  • Newsletter

Company

  • About Us
  • Why ZyVOP
  • Developer API & CLI
  • Write for Us
  • Contact

Connect

  • Privacy Policy
  • Terms of Service
  • Cookie Policy
  • DMCA Policy
  • Code of Conduct

ยฉ 2026 ZyVOP. Developer Publishing Hub.

Zero paywalls ยท Full content ownership
All systems operational
HomeAsync Svelte: Using await Directly in Your Components

Async Svelte: Using await Directly in Your Components

Danny Holloran
Danny Holloran
Senior Developer
August 31, 2026
4 min read
Async Svelte: Using await Directly in Your Components
#JavaScript#svelte#performance
๐Ÿ‘1

Every Svelte codebase eventually grows a little pile of scaffolding around asynchronous data. A let data = $state(null), an $effect that fetches and assigns, a loading flag, an error flag, and a {#if loading} in the template. Or the {#await} block, which is fine for one promise but nests badly the moment you need two. Either way, you end up writing plumbing rather than describing your UI.

Since Svelte 5.36, you can skip most of that. The await keyword works in three places it previously did not: at the top level of a component's <script>, inside $derived(...), and directly in your markup. It is still behind an experimental flag, but the design is worth understanding now because it changes how you think about loading states.

Turning it on, and what changes

Async Svelte is opt-in. Add experimental.async wherever you configure the compiler, which usually means svelte.config.js:

export default {
  compilerOptions: {
    experimental: {
      async: true,
    },
  },
};

The docs say this flag disappears in Svelte 6, so today's opt-in is tomorrow's default. Once it is on, this is legal:

<script>
  let a = $state(1);
  let b = $state(2);

  async function add(a, b) {
    await new Promise((f) => setTimeout(f, 500));
    return a + b;
  }
</script>

<input type="number" bind:value={a} />
<input type="number" bind:value={b} />

<p>{a} + {b} = {await add(a, b)}</p>

The interesting part is what does not happen. Bump a to 2 and the paragraph does not flash 2 + 2 = 3 while the promise is in flight. Svelte holds the whole update until add(a, b) resolves, then swaps everything at once. That is the headline feature: synchronized updates. You never render a torn UI where half the values are new and half are stale, which is exactly the bug that loading flags exist to paper over.

Updates can also overlap. A fast update lands while a slower earlier one is still running, so a quick keystroke is not stuck behind a slow one.

Concurrency is automatic, waterfalls are not

Two independent await expressions in markup run in parallel, even though they read as sequential:

<p>{await one(x)}</p>
<p>{await two(y)}</p>

Both kick off immediately. The same is not true inside your <script>, where await behaves like ordinary JavaScript and runs top to bottom. Svelte will warn you about this with an await_waterfall warning when you write something like:

let a = $derived(await one(x));
let b = $derived(await two(y));

Here b is not created until a resolves. Once both exist they update independently, but that first pass is a waterfall. If you have seen the same class of bug in a React useEffect chain, this is the familiar shape with a compiler warning attached.

Loading states move into boundaries

With no loading variable to hang a spinner on, placeholder UI moves to <svelte:boundary> and its pending snippet:

<svelte:boundary>
  <p>{await delayed('hello!')}</p>

  {#snippet pending()}
    <p>loading...</p>
  {/snippet}
</svelte:boundary>

The pending snippet shows when the boundary is first created and stays until every await inside it resolves. It deliberately does not reappear for later updates, since those are globally coordinated and rendering a full-page skeleton on every keystroke would be worse than useless.

For subsequent async work, $effect.pending() tells you how many promises are outstanding in the current boundary, not counting child boundaries. That is what you reach for when you want a small "validating..." spinner next to a form field rather than blanking the section. There is also settled(), a promise that resolves once state changes and their async consequences have been flushed to the DOM.

Errors get the same treatment. Anything thrown inside an await expression bubbles to the nearest boundary, where a failed snippet receives the error and a reset function. Worth remembering: boundaries catch errors during rendering and effects, not errors from event handlers or a stray setTimeout.

The parts still in motion

This is experimental, and the docs are direct about it: the details of await handling and $effect.pending() can change outside a semver major. Effect ordering already shifts when the flag is on, with block effects like {#if} and {#each} running before $effect.pre in the same component.

Server rendering works through an awaited render(...), though frameworks handle that for you. Today a boundary with a pending snippet renders that snippet during SSR and skips its contents, with streaming planned but not shipped. Svelte 5.42 also added fork(...), which speculatively runs async work you expect to need soon, and SvelteKit is the intended consumer for preloading on hover or focus.

If you maintain a Svelte app, the useful move right now is not a rewrite. Turn the flag on in a branch, pick one component with the most loading and error bookkeeping, and see how much of it disappears. The await expressions docs and the <svelte:boundary> reference are short enough to read in one sitting.

Comments (0)

Login to post a comment.

Danny Holloran
Danny Holloran

Senior Developer

Senior Frontend & Fullstack Developer with 14+ years building performant, scalable web applications. Passionate about architecture, mentorship, and finding the right tool for the job.

Subscribe to Danny Holloran's Newsletter

More from Danny Holloran

View profile

The Web Locks API: One Tab Does the Work, the Rest Wait

Your app already runs in five tabs at once, and they all think they are in charge. The Web Locks API gives the browser a real mutex so only one of them does the work.

4 minAug 29

Form-Associated Custom Elements: Web Components That Belong in a Form

Custom elements can be real form controls. Here is how formAssociated, ElementInternals, and setValidity retire the hidden-input hack for good.

3 minAug 29

Next.js Partial Prefetching: One Shell Per Route, Not One Per Link

Next.js 16.3 stops firing a prefetch request for every link in the viewport and caches one reusable loading shell per route instead. Here's what changes and how to turn it on.

4 minAug 24

Declarative Partial Updates: Out-of-Order HTML Streaming Without a Framework

Chrome 148 ships experimental support for filling HTML placeholders out of order and streaming markup into the DOM. Here's how the new template-for and streamHTML APIs work, and what they replace.

3 minAug 22

React's Activity Component: Hide UI Without Losing Its State

React 19.2's Activity component hides a subtree instead of unmounting it, so state, scroll position, and DOM survive the round trip. Here's how it behaves, what it does to your Effects, and where it costs you.

3 minAug 18