-
-
Notifications
You must be signed in to change notification settings - Fork 4.6k
docs: async stuff #16376
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
docs: async stuff #16376
Changes from all commits
e313292
95360aa
8c6638c
e5e1698
ad2b7b3
9a53553
25b8c33
4567dac
bbd42e2
4d3bd63
14f3458
2f2ca5d
e686e94
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'svelte': patch | ||
--- | ||
|
||
fix: add `$effect.pending()` to types |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
--- | ||
title: await | ||
--- | ||
|
||
As of Svelte 5.36, you can use the `await` keyword inside your components in three places where it was previously unavailable: | ||
|
||
- at the top level of your component's `<script>` | ||
- inside `$derived(...)` declarations | ||
- inside your markup | ||
|
||
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`: | ||
|
||
```js | ||
/// file: svelte.config.js | ||
export default { | ||
compilerOptions: { | ||
experimental: { | ||
async: true | ||
} | ||
} | ||
}; | ||
``` | ||
|
||
The experimental flag will be removed in Svelte 6. | ||
|
||
## Boundaries | ||
|
||
Currently, you can only use `await` inside a [`<svelte:boundary>`](svelte-boundary) with a `pending` snippet: | ||
|
||
```svelte | ||
<svelte:boundary> | ||
<MyApp /> | ||
|
||
{#snippet pending()} | ||
<p>loading...</p> | ||
{/snippet} | ||
</svelte:boundary> | ||
``` | ||
|
||
This restriction will be lifted once Svelte supports asynchronous server-side rendering (see [caveats](#Caveats)). | ||
|
||
> [!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. | ||
|
||
## Synchronized updates | ||
|
||
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)... | ||
|
||
```svelte | ||
<script> | ||
let a = $state(1); | ||
let b = $state(2); | ||
|
||
async function add(a, b) { | ||
await new Promise((f) => setTimeout(f, 500)); // artificial delay | ||
return a + b; | ||
} | ||
</script> | ||
|
||
<input type="number" bind:value={a}> | ||
<input type="number" bind:value={b}> | ||
|
||
<p>{a} + {b} = {await add(a, b)}</p> | ||
``` | ||
|
||
...if you increment `a`, the contents of the `<p>` will _not_ immediately update to read this — | ||
|
||
```html | ||
<p>2 + 2 = 3</p> | ||
``` | ||
|
||
— instead, the text will update to `2 + 2 = 4` when `add(a, b)` resolves. | ||
|
||
Updates can overlap — a fast update will be reflected in the UI while an earlier slow update is still ongoing. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this would be useful to have an example of. What do you think about one or the other of these two "adding with an artificial delay" examples using a randomized delay? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (Also, some more explicit information about what happens to the slow update would be nice. I'm imagining it's just getting discarded?) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what about just linking to something like this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Rich-Harris I don't think that quite answers the question I immediately get from this, which is "If update 1 finishes after update 2, does it clobber the result of update 2 or get discarded"? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, maybe, for part of it. Synchronous updates (or as close as we currently have to synchronous updates) doesn't feel quite the same in terms of being a 'fast' update as a fast async update would, I don't think. I don't know how fancy we want to get here in these examples. There are a couple of things going on here. One is synchronous updates continuing to happen while waiting for async updates. One is two different independent async updates happening independently. And one is multiple async updates of the same data happening, possibly completing out of order. If you want to gloss over at least some of these in this initial pass at the documentation, that's probably a reasonable idea. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I do like the idea of not getting too bogged down in details because a) they might change before the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (and if people want the answer to questions like 'does it clobber the result of update 2 ...' they can just try it and see) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will merge in the meantime, we can iterate as needed |
||
|
||
## Concurrency | ||
|
||
Svelte will do as much asynchronous work as it can in parallel. For example if you have two `await` expressions in your markup... | ||
|
||
```svelte | ||
<p>{await one()}</p> | ||
<p>{await two()}</p> | ||
``` | ||
|
||
...both functions will run at the same time, as they are independent expressions, even though they are _visually_ sequential. | ||
|
||
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: | ||
|
||
```js | ||
async function one() { return 1; } | ||
async function two() { return 2; } | ||
// ---cut--- | ||
// these will run sequentially the first time, | ||
// but will update independently | ||
let a = $derived(await one()); | ||
let b = $derived(await two()); | ||
``` | ||
|
||
> [!NOTE] If you write code like this, expect Svelte to give you an [`await_waterfall`](runtime-warnings#Client-warnings-await_waterfall) warning | ||
|
||
## Indicating loading states | ||
|
||
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). | ||
|
||
You can also use [`settled()`](svelte#settled) to get a promise that resolves when the current update is complete: | ||
|
||
```js | ||
let color = 'red'; | ||
let answer = -1; | ||
let updating = false; | ||
// ---cut--- | ||
import { tick, settled } from 'svelte'; | ||
|
||
async function onclick() { | ||
updating = true; | ||
|
||
// without this, the change to `updating` will be | ||
// grouped with the other changes, meaning it | ||
// won't be reflected in the UI | ||
await tick(); | ||
|
||
color = 'octarine'; | ||
answer = 42; | ||
|
||
await settled(); | ||
|
||
// any updates affected by `color` or `answer` | ||
// have now been applied | ||
updating = false; | ||
} | ||
``` | ||
|
||
## Error handling | ||
|
||
Errors in `await` expressions will bubble to the nearest [error boundary](svelte-boundary). | ||
|
||
## Caveats | ||
|
||
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. | ||
|
||
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. | ||
|
||
## Breaking changes | ||
|
||
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). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm trying unsuccessfully to think of a non-contrived example that doesn't just use 0 vs >0. I'm guessing that's exactly how we ended up with this contrived example here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could imagine someone doing something cute like
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh. Cute, okay. My comment/question was less of a 'why does this feature exist?' and more of a 'what can we show here in the docs that doesn't feel contrived?'. I don't know that we'd want the distraction of an example like the one you just gave, though. I'm not sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, that was my thought process too — figured this would get the point across most concisely
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thought about something like this and discovered that a) it doesn't decrement when it should and b) there's some weird NaN action happening 🤔
investigating