Skip to content

Commit 9f59122

Browse files
docs: async stuff (#16376)
* WIP async docs * WIP * docs * update $effect namespace * changeset * oops * typo * fixes * tweak * missing link * fix * tweak * Update documentation/docs/03-template-syntax/19-await-expressions.md Co-authored-by: Conduitry <[email protected]> --------- Co-authored-by: Conduitry <[email protected]>
1 parent b23f1e0 commit 9f59122

File tree

7 files changed

+206
-6
lines changed

7 files changed

+206
-6
lines changed

.changeset/long-drinks-reply.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: add `$effect.pending()` to types

documentation/docs/02-runes/02-$state.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ class Todo {
119119
}
120120
```
121121

122-
> Svelte provides reactive implementations of built-in classes like `Set` and `Map` that can be imported from [`svelte/reactivity`](svelte-reactivity).
122+
> [NOTE!] Svelte provides reactive implementations of built-in classes like `Set` and `Map` that can be imported from [`svelte/reactivity`](svelte-reactivity).
123123
124124
## `$state.raw`
125125

documentation/docs/02-runes/04-$effect.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,21 @@ The `$effect.tracking` rune is an advanced feature that tells you whether or not
221221

222222
It is used to implement abstractions like [`createSubscriber`](/docs/svelte/svelte-reactivity#createSubscriber), which will create listeners to update reactive values but _only_ if those values are being tracked (rather than, for example, read inside an event handler).
223223

224+
## `$effect.pending`
225+
226+
When using [`await`](await-expressions) in components, the `$effect.pending()` rune tells you how many promises are pending in the current [boundary](svelte-boundary), not including child boundaries ([demo](/playground/untitled#H4sIAAAAAAAAE3WRMU_DMBCF_8rJdHDUqilILGkaiY2RgY0yOPYZWbiOFV8IleX_jpMUEAIWS_7u-d27c2ROnJBV7B6t7WDsequAozKEqmAbpo3FwKqnyOjsJ90EMr-8uvN-G97Q0sRaEfAvLjtH6CjbsDrI3nhqju5IFgkEHGAVSBDy62L_SdtvejPTzEU4Owl6cJJM50AoxcUG2gLiVM31URgChyM89N3JBORcF3BoICA9mhN2A3G9gdvdrij2UJYgejLaSCMsKLTivNj0SEOf7WEN7ZwnHV1dfqd2dTsQ5QCdk9bI10PkcxexXqcmH3W51Jt_le2kbH8os9Y3UaTcNLYpDx-Xab6GTHXpZ128MhpWqDVK2np0yrgXXqQpaLa4APDLBkIF8bd2sYql0Sn_DeE7sYr6AdNzvgljR-MUq7SwAdMHeUtgHR4CAAA=)):
227+
228+
```svelte
229+
<button onclick={() => a++}>a++</button>
230+
<button onclick={() => b++}>b++</button>
231+
232+
<p>{a} + {b} = {await add(a, b)}</p>
233+
234+
{#if $effect.pending()}
235+
<p>pending promises: {$effect.pending()}</p>
236+
{/if}
237+
```
238+
224239
## `$effect.root`
225240

226241
The `$effect.root` rune is an advanced feature that creates a non-tracked scope that doesn't auto-cleanup. This is useful for nested effects that you want to manually control. This rune also allows for the creation of effects outside of the component initialisation phase.
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
---
2+
title: await
3+
---
4+
5+
As of Svelte 5.36, you can use the `await` keyword inside your components in three places where it was previously unavailable:
6+
7+
- at the top level of your component's `<script>`
8+
- inside `$derived(...)` declarations
9+
- inside your markup
10+
11+
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`:
12+
13+
```js
14+
/// file: svelte.config.js
15+
export default {
16+
compilerOptions: {
17+
experimental: {
18+
async: true
19+
}
20+
}
21+
};
22+
```
23+
24+
The experimental flag will be removed in Svelte 6.
25+
26+
## Boundaries
27+
28+
Currently, you can only use `await` inside a [`<svelte:boundary>`](svelte-boundary) with a `pending` snippet:
29+
30+
```svelte
31+
<svelte:boundary>
32+
<MyApp />
33+
34+
{#snippet pending()}
35+
<p>loading...</p>
36+
{/snippet}
37+
</svelte:boundary>
38+
```
39+
40+
This restriction will be lifted once Svelte supports asynchronous server-side rendering (see [caveats](#Caveats)).
41+
42+
> [!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.
43+
44+
## Synchronized updates
45+
46+
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)...
47+
48+
```svelte
49+
<script>
50+
let a = $state(1);
51+
let b = $state(2);
52+
53+
async function add(a, b) {
54+
await new Promise((f) => setTimeout(f, 500)); // artificial delay
55+
return a + b;
56+
}
57+
</script>
58+
59+
<input type="number" bind:value={a}>
60+
<input type="number" bind:value={b}>
61+
62+
<p>{a} + {b} = {await add(a, b)}</p>
63+
```
64+
65+
...if you increment `a`, the contents of the `<p>` will _not_ immediately update to read this —
66+
67+
```html
68+
<p>2 + 2 = 3</p>
69+
```
70+
71+
— instead, the text will update to `2 + 2 = 4` when `add(a, b)` resolves.
72+
73+
Updates can overlap — a fast update will be reflected in the UI while an earlier slow update is still ongoing.
74+
75+
## Concurrency
76+
77+
Svelte will do as much asynchronous work as it can in parallel. For example if you have two `await` expressions in your markup...
78+
79+
```svelte
80+
<p>{await one()}</p>
81+
<p>{await two()}</p>
82+
```
83+
84+
...both functions will run at the same time, as they are independent expressions, even though they are _visually_ sequential.
85+
86+
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:
87+
88+
```js
89+
async function one() { return 1; }
90+
async function two() { return 2; }
91+
// ---cut---
92+
// these will run sequentially the first time,
93+
// but will update independently
94+
let a = $derived(await one());
95+
let b = $derived(await two());
96+
```
97+
98+
> [!NOTE] If you write code like this, expect Svelte to give you an [`await_waterfall`](runtime-warnings#Client-warnings-await_waterfall) warning
99+
100+
## Indicating loading states
101+
102+
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).
103+
104+
You can also use [`settled()`](svelte#settled) to get a promise that resolves when the current update is complete:
105+
106+
```js
107+
let color = 'red';
108+
let answer = -1;
109+
let updating = false;
110+
// ---cut---
111+
import { tick, settled } from 'svelte';
112+
113+
async function onclick() {
114+
updating = true;
115+
116+
// without this, the change to `updating` will be
117+
// grouped with the other changes, meaning it
118+
// won't be reflected in the UI
119+
await tick();
120+
121+
color = 'octarine';
122+
answer = 42;
123+
124+
await settled();
125+
126+
// any updates affected by `color` or `answer`
127+
// have now been applied
128+
updating = false;
129+
}
130+
```
131+
132+
## Error handling
133+
134+
Errors in `await` expressions will bubble to the nearest [error boundary](svelte-boundary).
135+
136+
## Caveats
137+
138+
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.
139+
140+
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.
141+
142+
## Breaking changes
143+
144+
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).

documentation/docs/05-special-elements/01-svelte-boundary.md

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,41 @@ title: <svelte:boundary>
99
> [!NOTE]
1010
> This feature was added in 5.3.0
1111
12-
Boundaries allow you to guard against errors in part of your app from breaking the app as a whole, and to recover from those errors.
12+
Boundaries allow you to 'wall off' parts of your app, so that you can:
1313

14-
If an error occurs while rendering or updating the children of a `<svelte:boundary>`, or running any [`$effect`]($effect) functions contained therein, the contents will be removed.
14+
- provide UI that should be shown when [`await`](await-expressions) expressions are first resolving
15+
- handle errors that occur during rendering or while running effects, and provide UI that should be rendered when an error happens
1516

16-
Errors occurring outside the rendering process (for example, in event handlers or after a `setTimeout` or async work) are _not_ caught by error boundaries.
17+
If a boundary handles an error (with a `failed` snippet or `onerror` handler, or both) its existing content will be removed.
18+
19+
> [!NOTE] Errors occurring outside the rendering process (for example, in event handlers or after a `setTimeout` or async work) are _not_ caught by error boundaries.
1720
1821
## Properties
1922

20-
For the boundary to do anything, one or both of `failed` and `onerror` must be provided.
23+
For the boundary to do anything, one or more of the following must be provided.
24+
25+
### `pending`
26+
27+
As of Svelte 5.36, boundaries with a `pending` snippet can contain [`await`](await-expressions) expressions. This snippet will be shown when the boundary is first created, and will remain visible until all the `await` expressions inside the boundary have resolved ([demo](/playground/untitled#H4sIAAAAAAAAE21QQW6DQAz8ytY9BKQVpFdKkPqDHnorPWzAaSwt3tWugUaIv1eE0KpKD5as8YxnNBOw6RAKKOOAVrA4up5bEy6VGknOyiO3xJ8qMnmPAhpOZDFC8T6BXPyiXADQ258X77P1FWg4moj_4Y1jQZZ49W0CealqruXUcyPkWLVozQXbZDC2R606spYiNo7bqA7qab_fp2paFLUElD6wYhzVa3AdRUySgNHZAVN1qDZaLRHljTp0vSTJ9XJjrSbpX5f0eZXN6zLXXOa_QfmurIVU-moyoyH5ib87o7XuYZfOZe6vnGWmx1uZW7lJOq9upa-sMwuUZdkmmfIbfQ1xZwwaBL8ECgk9zh8axJAdiVsoTsZGnL8Bg4tX_OMBAAA=)):
28+
29+
```svelte
30+
<svelte:boundary>
31+
<p>{await delayed('hello!')}</p>
32+
33+
{#snippet pending()}
34+
<p>loading...</p>
35+
{/snippet}
36+
</svelte:boundary>
37+
```
38+
39+
The `pending` snippet will _not_ be shown for subsequent async updates — for these, you can use [`$effect.pending()`]($effect#$effect.pending).
40+
41+
> [!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.
42+
2143

2244
### `failed`
2345

24-
If a `failed` snippet is provided, it will be rendered with the error that was thrown, and a `reset` function that recreates the contents ([demo](/playground/hello-world#H4sIAAAAAAAAE3VRy26DMBD8lS2tFCIh6JkAUlWp39Cq9EBg06CAbdlLArL87zWGKk8ORnhmd3ZnrD1WtOjFXqKO2BDGW96xqpBD5gXerm5QefG39mgQY9EIWHxueRMinLosti0UPsJLzggZKTeilLWgLGc51a3gkuCjKQ7DO7cXZotgJ3kLqzC6hmex1SZnSXTWYHcrj8LJjWTk0PHoZ8VqIdCOKayPykcpuQxAokJaG1dGybYj4gw4K5u6PKTasSbjXKgnIDlA8VvUdo-pzonraBY2bsH7HAl78mKSHZpgIcuHjq9jXSpZSLixRlveKYQUXhQVhL6GPobXAAb7BbNeyvNUs4qfRg3OnELLj5hqH9eQZqCnoBwR9lYcQxuVXeBzc8kMF8yXY4yNJ5oGiUzP_aaf_waTRGJib5_Ad3P_vbCuaYxzeNpbU0eUMPAOKh7Yw1YErgtoXyuYlPLzc10_xo_5A91zkQL_AgAA)):
46+
If a `failed` snippet is provided, it will be rendered when an error is thrown inside the boundary, with the `error` and a `reset` function that recreates the contents ([demo](/playground/hello-world#H4sIAAAAAAAAE3VRy26DMBD8lS2tFCIh6JkAUlWp39Cq9EBg06CAbdlLArL87zWGKk8ORnhmd3ZnrD1WtOjFXqKO2BDGW96xqpBD5gXerm5QefG39mgQY9EIWHxueRMinLosti0UPsJLzggZKTeilLWgLGc51a3gkuCjKQ7DO7cXZotgJ3kLqzC6hmex1SZnSXTWYHcrj8LJjWTk0PHoZ8VqIdCOKayPykcpuQxAokJaG1dGybYj4gw4K5u6PKTasSbjXKgnIDlA8VvUdo-pzonraBY2bsH7HAl78mKSHZpgIcuHjq9jXSpZSLixRlveKYQUXhQVhL6GPobXAAb7BbNeyvNUs4qfRg3OnELLj5hqH9eQZqCnoBwR9lYcQxuVXeBzc8kMF8yXY4yNJ5oGiUzP_aaf_waTRGJib5_Ad3P_vbCuaYxzeNpbU0eUMPAOKh7Yw1YErgtoXyuYlPLzc10_xo_5A91zkQL_AgAA)):
2547

2648
```svelte
2749
<svelte:boundary>

packages/svelte/src/ambient.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,13 @@ declare namespace $effect {
255255
*/
256256
export function pre(fn: () => void | (() => void)): void;
257257

258+
/**
259+
* Returns the number of promises that are pending in the current boundary, not including child boundaries.
260+
*
261+
* https://svelte.dev/docs/svelte/$effect#$effect.pending
262+
*/
263+
export function pending(): number;
264+
258265
/**
259266
* The `$effect.tracking` rune is an advanced feature that tells you whether or not the code is running inside a tracking context, such as an effect or inside your template.
260267
*

packages/svelte/types/index.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3325,6 +3325,13 @@ declare namespace $effect {
33253325
*/
33263326
export function pre(fn: () => void | (() => void)): void;
33273327

3328+
/**
3329+
* Returns the number of promises that are pending in the current boundary, not including child boundaries.
3330+
*
3331+
* https://svelte.dev/docs/svelte/$effect#$effect.pending
3332+
*/
3333+
export function pending(): number;
3334+
33283335
/**
33293336
* The `$effect.tracking` rune is an advanced feature that tells you whether or not the code is running inside a tracking context, such as an effect or inside your template.
33303337
*

0 commit comments

Comments
 (0)