await
从 Svelte 5.36 开始,你可以在组件内部的三个地方使用 await 关键字,而之前该关键字不可用:
¥As of Svelte 5.36, you can use the await keyword inside your components in three places where it was previously unavailable:
在组件的
<script>顶层¥at the top level of your component’s
<script>在
$derived(...)声明内¥inside
$derived(...)declarations在你的标记内
¥inside your markup
此功能目前处于实验阶段,你必须在 Svelte 的任何 configure 版本(通常是 svelte.config.js)中添加 experimental.async 选项来选择加入:
¥This feature is currently experimental, and you must opt in by adding the experimental.async option wherever you configure Svelte, usually svelte.config.js:
export default {
compilerOptions: {
experimental: {
async: boolean;
};
}
compilerOptions: {
experimental: {
async: boolean;
}
experimental: {
async: booleanasync: true
}
}
};实验性标志将在 Svelte 6 中被移除。
¥The experimental flag will be removed in Svelte 6.
同步更新(Synchronized updates)
¥Synchronized updates
当 await 表达式依赖于特定状态时,对该状态的更改将直到异步工作完成才会反映在 UI 中,这样 UI 就不会处于不一致的状态。换句话说,在像 this 这样的例子中……
¥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...
<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>...如果增加 a,<p> 的内容不会立即更新以读取此值——
¥...if you increment a, the contents of the <p> will not immediately update to read this —
<p>2 + 2 = 3</p>— 相反,当 add(a, b) 解析时,文本将更新为 2 + 2 = 4。
¥— instead, the text will update to 2 + 2 = 4 when add(a, b) resolves.
更新可以重叠 - 快速更新将反映在 UI 中,而之前的慢速更新仍在进行中。
¥Updates can overlap — a fast update will be reflected in the UI while an earlier slow update is still ongoing.
并发性(Concurrency)
¥Concurrency
Svelte 将尽可能多地并行执行异步工作。例如,如果你的标记中有两个 await 表达式……
¥Svelte will do as much asynchronous work as it can in parallel. For example if you have two await expressions in your markup...
<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.
这不适用于 <script> 内部或异步函数内部的顺序 await 表达式 - 这些表达式的运行方式与其他异步 JavaScript 一样。一个例外是,独立的 $derived 表达式将独立更新,即使它们在首次创建时会按顺序运行:
¥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:
// these will run sequentially the first time,
// but will update independently
let let a: numbera = function $derived<number>(expression: number): number
namespace $derived
Declares derived state, i.e. one that depends on other state variables.
The expression inside $derived(...) should be free of side-effects.
Example:
let double = $derived(count * 2);
$derived(await function one(): Promise<number>one());
let let b: numberb = function $derived<number>(expression: number): number
namespace $derived
Declares derived state, i.e. one that depends on other state variables.
The expression inside $derived(...) should be free of side-effects.
Example:
let double = $derived(count * 2);
$derived(await function two(): Promise<number>two());如果你编写这样的代码,Svelte 会给你一个
await_waterfall警告。
加载状态指示(Indicating loading states)
¥Indicating loading states
要渲染占位符 UI,你可以使用 pending 代码片段将内容封装在 <svelte:boundary> 中。这将在首次创建边界时显示,但不会显示在后续更新中,因为后续更新是全局协调的。
¥To render placeholder UI, you can wrap content in a <svelte:boundary> with a pending snippet. This will be shown when the boundary is first created, but not for subsequent updates, which are globally coordinated.
在边界内容首次解析并替换 pending 代码片段后,你可以使用 $effect.pending() 检测后续的异步工作。例如,你可以使用它来在表单字段旁边显示 “我们正在异步验证你的输入” 旋转器。
¥After the contents of a boundary have resolved for the first time and have replaced the pending snippet, you can detect subsequent async work with $effect.pending(). This is what you would use to display a “we’re asynchronously validating your input” spinner next to a form field, for example.
你还可以使用 settled() 获取在当前更新完成时解析的 Promise:
¥You can also use settled() to get a promise that resolves when the current update is complete:
import { function tick(): Promise<void>Returns a promise that resolves once any pending state changes have been applied.
tick, function settled(): Promise<void>Returns a promise that resolves once any state changes, and asynchronous work resulting from them,
have resolved and the DOM has been updated
settled } from 'svelte';
async function function onclick(): Promise<void>onclick() {
let updating: booleanupdating = true;
// without this, the change to `updating` will be
// grouped with the other changes, meaning it
// won't be reflected in the UI
await function tick(): Promise<void>Returns a promise that resolves once any pending state changes have been applied.
tick();
let color: stringcolor = 'octarine';
let answer: numberanswer = 42;
await function settled(): Promise<void>Returns a promise that resolves once any state changes, and asynchronous work resulting from them,
have resolved and the DOM has been updated
settled();
// any updates affected by `color` or `answer`
// have now been applied
let updating: booleanupdating = false;
}错误处理(Error handling)
¥Error handling
await 表达式中的错误会冒泡到最近的 错误边界。
¥Errors in await expressions will bubble to the nearest error boundary.
服务器端渲染(Server-side rendering)
¥Server-side rendering
Svelte 使用 render(...) API 支持异步服务器端渲染 (SSR)。要使用它,只需等待返回值即可:
¥Svelte supports asynchronous server-side rendering (SSR) with the render(...) API. To use it, simply await the return value:
import { function render<Comp extends SvelteComponent<any> | Component<any>, Props extends ComponentProps<Comp> = ComponentProps<Comp>>(...args: {} extends Props ? [component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp, options?: {
props?: Omit<Props, "$$slots" | "$$events">;
context?: Map<any, any>;
idPrefix?: string;
}] : [component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp, options: {
props: Omit<Props, "$$slots" | "$$events">;
context?: Map<any, any>;
idPrefix?: string;
}]): RenderOutput
Only available on the server and when compiling with the server option.
Takes a component and returns an object with body and head properties on it, which you can use to populate the HTML when server-rendering your app.
render } from 'svelte/server';
import type App = SvelteComponent<Record<string, any>, any, any>
const App: LegacyComponentType
App from './App.svelte';
const { const head: stringHTML that goes into the <head>
head, const body: stringHTML that goes somewhere into the <body>
body } = await render<SvelteComponent<Record<string, any>, any, any>, Record<string, any>>(component: ComponentType<SvelteComponent<Record<string, any>, any, any>>, options?: {
...;
} | undefined): RenderOutput
Only available on the server and when compiling with the server option.
Takes a component and returns an object with body and head properties on it, which you can use to populate the HTML when server-rendering your app.
render(const App: LegacyComponentTypeApp);如果你使用的是像 SvelteKit 这样的框架,则此操作将由你完成。
如果在 SSR 期间遇到带有 pending 代码片段的 <svelte:boundary>,则会渲染该代码片段,而忽略其余内容。所有在 pending 代码片段边界之外遇到的 await 表达式,都将在 await render(...) 返回之前解析并渲染其内容。
¥If a <svelte:boundary> with a pending snippet is encountered during SSR, that snippet will be rendered while the rest of the content is ignored. All await expressions encountered outside boundaries with pending snippets will resolve and render their contents prior to await render(...) returning.
未来,我们计划添加一个在后台渲染内容的流式实现。
Forking
5.42 版本中新增的 fork(...) API 可以运行你预期在不久的将来发生的 await 表达式。这主要用于像 SvelteKit 这样的框架,用于在(例如)用户发出导航意图时实现预加载。
¥The fork(...) API, added in 5.42, makes it possible to run await expressions that you expect to happen in the near future. This is mainly intended for frameworks like SvelteKit to implement preloading when (for example) users signal an intent to navigate.
<script>
import { fork } from 'svelte';
import Menu from './Menu.svelte';
let open = $state(false);
/** @type {import('svelte').Fork | null} */
let pending = null;
function preload() {
pending ??= fork(() => {
open = true;
});
}
function discard() {
pending?.discard();
pending = null;
}
</script>
<button
onfocusin={preload}
onfocusout={discard}
onpointerenter={preload}
onpointerleave={discard}
onclick={() => {
pending?.commit();
pending = null;
// in case `pending` didn't exist
// (if it did, this is a no-op)
open = true;
}}
>open menu</button>
{#if open}
<!-- any async work inside this component will start
as soon as the fork is created -->
<Menu onclose={() => open = false} />
{/if}注意事项(Caveats)
¥Caveats
作为一项实验性功能,await 的处理方式(以及与 $effect.pending() 类似的相关 API)的细节可能会在 semver 主要版本之外发生重大变更,但我们打算将此类变更保持在最低限度。
¥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.
重大变更(Breaking changes)
¥Breaking changes
当 experimental.async 选项为 true 时,Effect 的运行顺序会略有不同。具体来说,现在在同一组件中,像 {#if ...} 和 {#each ...} 这样的块 effect 会在 $effect.pre 或 beforeUpdate 之前运行,这意味着在 非常罕见的情况 中,可以更新一个本不该存在的块,但前提是你必须在 effect 中更新状态(你应该避免的情况)。
¥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 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.