|
| 1 | +--- |
| 2 | +NOTE: do not edit this file, it is generated in apps/svelte.dev/scripts/sync-docs/index.ts |
| 3 | +title: await |
| 4 | +--- |
| 5 | + |
| 6 | +As of Svelte 5.36, you can use the `await` keyword inside your components in three places where it was previously unavailable: |
| 7 | + |
| 8 | +- at the top level of your component's `<script>` |
| 9 | +- inside `$derived(...)` declarations |
| 10 | +- inside your markup |
| 11 | + |
| 12 | +This feature is currently experimental, and you must opt in by adding the `experimental.async` option wherever you [configure](/docs/kit/configuration) Svelte, usually `svelte.config.js`: |
| 13 | + |
| 14 | +```js |
| 15 | +/// file: svelte.config.js |
| 16 | +export default { |
| 17 | + compilerOptions: { |
| 18 | + experimental: { |
| 19 | + async: true |
| 20 | + } |
| 21 | + } |
| 22 | +}; |
| 23 | +``` |
| 24 | + |
| 25 | +The experimental flag will be removed in Svelte 6. |
| 26 | + |
| 27 | +## Boundaries |
| 28 | + |
| 29 | +Currently, you can only use `await` inside a [`<svelte:boundary>`](svelte-boundary) with a `pending` snippet: |
| 30 | + |
| 31 | +```svelte |
| 32 | +<svelte:boundary> |
| 33 | + <MyApp /> |
| 34 | +
|
| 35 | + {#snippet pending()} |
| 36 | + <p>loading...</p> |
| 37 | + {/snippet} |
| 38 | +</svelte:boundary> |
| 39 | +``` |
| 40 | + |
| 41 | +This restriction will be lifted once Svelte supports asynchronous server-side rendering (see [caveats](#Caveats)). |
| 42 | + |
| 43 | +> [!NOTE] In the [playground](/playground), your app is rendered inside a boundary with an empty pending snippet, so that you can use `await` without having to create one. |
| 44 | +
|
| 45 | +## Synchronized updates |
| 46 | + |
| 47 | +When an `await` expression depends on a particular piece of state, changes to that state will not be reflected in the UI until the asynchronous work has completed, so that the UI is not left in an inconsistent state. In other words, in an example like [this](/playground/untitled#H4sIAAAAAAAAE42QsWrDQBBEf2VZUkhYRE4gjSwJ0qVMkS6XYk9awcFpJe5Wdoy4fw-ycdykSPt2dpiZFYVGxgrf2PsJTlPwPWTcO-U-xwIH5zli9bminudNtwEsbl-v8_wYj-x1Y5Yi_8W7SZRFI1ZYxy64WVsjRj0rEDTwEJWUs6f8cKP2Tp8vVIxSPEsHwyKdukmA-j6jAmwO63Y1SidyCsIneA_T6CJn2ZBD00Jk_XAjT4tmQwEv-32eH6AsgYK6wXWOPPTs6Xy1CaxLECDYgb3kSUbq8p5aaifzorCt0RiUZbQcDIJ10ldH8gs3K6X2Xzqbro5zu1KCHaw2QQPrtclvwVSXc2sEC1T-Vqw0LJy-ClRy_uSkx2ogHzn9ADZ1CubKAQAA)... |
| 48 | + |
| 49 | +```svelte |
| 50 | +<script> |
| 51 | + let a = $state(1); |
| 52 | + let b = $state(2); |
| 53 | +
|
| 54 | + async function add(a, b) { |
| 55 | + await new Promise((f) => setTimeout(f, 500)); // artificial delay |
| 56 | + return a + b; |
| 57 | + } |
| 58 | +</script> |
| 59 | +
|
| 60 | +<input type="number" bind:value={a}> |
| 61 | +<input type="number" bind:value={b}> |
| 62 | +
|
| 63 | +<p>{a} + {b} = {await add(a, b)}</p> |
| 64 | +``` |
| 65 | + |
| 66 | +...if you increment `a`, the contents of the `<p>` will _not_ immediately update to read this — |
| 67 | + |
| 68 | +```html |
| 69 | +<p>2 + 2 = 3</p> |
| 70 | +``` |
| 71 | + |
| 72 | +— instead, the text will update to `2 + 2 = 4` when `add(a, b)` resolves. |
| 73 | + |
| 74 | +Updates can overlap — a fast update will be reflected in the UI while an earlier slow update is still ongoing. |
| 75 | + |
| 76 | +## Concurrency |
| 77 | + |
| 78 | +Svelte will do as much asynchronous work as it can in parallel. For example if you have two `await` expressions in your markup... |
| 79 | + |
| 80 | +```svelte |
| 81 | +<p>{await one()}</p> |
| 82 | +<p>{await two()}</p> |
| 83 | +``` |
| 84 | + |
| 85 | +...both functions will run at the same time, as they are independent expressions, even though they are _visually_ sequential. |
| 86 | + |
| 87 | +This does not apply to sequential `await` expressions inside your `<script>` or inside async functions — these run like any other asynchronous JavaScript. An exception is that independent `$derived` expressions will update independently, even though they will run sequentially when they are first created: |
| 88 | + |
| 89 | +```js |
| 90 | +async function one() { return 1; } |
| 91 | +async function two() { return 2; } |
| 92 | +// ---cut--- |
| 93 | +// these will run sequentially the first time, |
| 94 | +// but will update independently |
| 95 | +let a = $derived(await one()); |
| 96 | +let b = $derived(await two()); |
| 97 | +``` |
| 98 | + |
| 99 | +> [!NOTE] If you write code like this, expect Svelte to give you an [`await_waterfall`](runtime-warnings#Client-warnings-await_waterfall) warning |
| 100 | +
|
| 101 | +## Indicating loading states |
| 102 | + |
| 103 | +In addition to the nearest boundary's [`pending`](svelte-boundary#Properties-pending) snippet, you can indicate that asynchronous work is ongoing with [`$effect.pending()`]($effect#$effect.pending). |
| 104 | + |
| 105 | +You can also use [`settled()`](svelte#settled) to get a promise that resolves when the current update is complete: |
| 106 | + |
| 107 | +```js |
| 108 | +let color = 'red'; |
| 109 | +let answer = -1; |
| 110 | +let updating = false; |
| 111 | +// ---cut--- |
| 112 | +import { tick, settled } from 'svelte'; |
| 113 | + |
| 114 | +async function onclick() { |
| 115 | + updating = true; |
| 116 | + |
| 117 | + // without this, the change to `updating` will be |
| 118 | + // grouped with the other changes, meaning it |
| 119 | + // won't be reflected in the UI |
| 120 | + await tick(); |
| 121 | + |
| 122 | + color = 'octarine'; |
| 123 | + answer = 42; |
| 124 | + |
| 125 | + await settled(); |
| 126 | + |
| 127 | + // any updates affected by `color` or `answer` |
| 128 | + // have now been applied |
| 129 | + updating = false; |
| 130 | +} |
| 131 | +``` |
| 132 | + |
| 133 | +## Error handling |
| 134 | + |
| 135 | +Errors in `await` expressions will bubble to the nearest [error boundary](svelte-boundary). |
| 136 | + |
| 137 | +## Caveats |
| 138 | + |
| 139 | +As an experimental feature, the details of how `await` is handled (and related APIs like `$effect.pending()`) are subject to breaking changes outside of a semver major release, though we intend to keep such changes to a bare minimum. |
| 140 | + |
| 141 | +Currently, server-side rendering is synchronous. If a `<svelte:boundary>` with a `pending` snippet is encountered during SSR, only the `pending` snippet will be rendered. |
| 142 | + |
| 143 | +## Breaking changes |
| 144 | + |
| 145 | +Effects run in a slightly different order when the `experimental.async` option is `true`. Specifically, _block_ effects like `{#if ...}` and `{#each ...}` now run before an `$effect.pre` or `beforeUpdate` in the same component, which means that in [very rare situations](/playground/untitled?#H4sIAAAAAAAAE22R3VLDIBCFX2WLvUhnTHsf0zre-Q7WmfwtFV2BgU1rJ5N3F0jaOuoVcPbw7VkYhK4_URTiGYkMnIyjDjLsFGO3EvdCKkIvipdB8NlGXxSCPt96snbtj0gctab2-J_eGs2oOWBE6VunLO_2es-EDKZ5x5ZhC0vPNWM2gHXGouNzAex6hHH1cPHil_Lsb95YT9VQX6KUAbS2DrNsBdsdDFHe8_XSYjH1SrhELTe3MLpsemajweiWVPuxHSbKNd-8eQTdE0EBf4OOaSg2hwNhhE_ABB_ulJzjj9FULvIcqgm5vnAqUB7wWFMfhuugQWkcAr8hVD-mq8D12kOep24J_IszToOXdveGDsuNnZwbJUNlXsKnhJdhUcTo42s41YpOSneikDV5HL8BktM6yRcCAAA=) it is possible to update a block that should no longer exist, but only if you update state inside an effect, [which you should avoid]($effect#When-not-to-use-$effect). |
0 commit comments