Svelte 5 等待异步:experimental.async 实验特性全景 —— 同步更新、fork 与 $effect.pending
Svelte 5 等待异步experimental.async 实验特性全景 —— 同步更新、fork 与 $effect.pending【免费下载链接】svelteweb development for the rest of us项目地址: https://gitcode.com/GitHub_Trending/sv/svelte从 Svelte 5.36 开始await可以从过去只能在 async 函数内部使用的 JS 语法扩展为可以直接出现在组件的script顶层、$derived(...)声明以及模板表达式中的响应式特性。它配合svelte:boundary的pending片段、$effect.pending()、settled()与 5.42 新增的fork(...)API让 Svelte 应用可以在保持 UI 状态一致性的前提下处理加载态、错误边界与数据预加载。本文基于仓库中的官方文档 19-await-expressions.md 展开并结合编译器与运行时源码讲清这一特性从哪里可用、如何保证一致性、如何显示 loading、出错后去哪、以及如何投机执行。需要说明的前提该特性目前处于实验阶段必须在编译器选项中显式开启experimental.async且实验开关计划在 Svelte 6 中移除届时await将直接可用。开启方式experimental.async 配置该功能必须在配置 Svelte 的地方通常是svelte.config.jsSvelteKit 项目中即 svelte.config.js中开启/// file: svelte.config.js export default { compilerOptions: { experimental: { async: true } } };开启后await在组件中可用位置变为三类组件script的顶层$derived(...)声明内部模板markup表达式内部。从源码看这一门槛在分析阶段就被强制校验。AwaitExpression.js 中当await出现在会挂起的位置顶层或表达式时会检查两个条件// disallow top-level await or await in template expressions // unless a) in runes mode and b) opted into experimental.async if (suspend) { if (!context.state.options.experimental.async) { e.experimental_async(node); } if (!context.state.analysis.runes) { e.legacy_await_invalid(node); } }也就是说未开启experimental.async时会报编译错误且 legacy非 runes模式下同样禁止。对应的运行时开关是一个全局标志flags/index.js 中的async_mode_flag表示编译时设置了experimental.asynctrue后续fork等 API 会依赖它做运行时校验见下文 Forking 一节。同步更新为什么 UI 不会闪出中间态这是整个特性最核心的语义。当一个await表达式依赖某份状态时该状态的变化不会立即反映到 UI 上而是要等异步工作完成后才一并更新从而避免 UI 停留在不一致状态。官方文档给出的例子!-- file: App.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 typenumber bind:value{a} input typenumber bind:value{b} p{a} {b} {await add(a, b)}/p如果把a从 1 加到 2p不会立刻显示成p2 2 3/p旧结果 新输入的组合而是等add(a, b)解析后整体更新为2 2 4。换句话说表达式中输入侧与结果侧被协调为同一次更新。文档同时指出更新可以重叠—— 一次快速的更新可以在更早一次慢速更新仍在进行时反映到 UI 上。从源码结构看这种全局协调正是基于批处理机制batch.js 中围绕Batch的调度逻辑负责把状态写入、派生重算与 DOM 应用组织进同一批次settled()见下文也返回该批次的完成 promise。并发哪些 await 并行、哪些串行Svelte 会尽可能并行地执行异步工作。例如模板中两个独立的await表达式p{await one(x)}/p p{await two(y)}/p虽然它们在视觉上顺序排列但one与two是相互独立的表达式会同时执行。但这条规则不适用于script顶层或 async 函数中顺序书写的await—— 那些仍然按普通异步 JavaScript 的规则串行执行。有一个重要例外相互独立的$derived表达式会各自独立更新尽管它们首次创建时会按顺序执行。文档给出的例子/** param {number} x */ async function one(x) { return x; } /** param {number} y */ async function two(y) { return y; } let x $state(1); let y $state(2); // b 在 a 解析完成前不会被创建 // 但一旦创建即使 x 和 y 同时变化 // 它们也会独立更新 let a $derived(await one(x)); let b $derived(await two(y));注意写出这种级联等待的代码时预期会收到await_waterfall警告。源码中该警告定义于 warnings.jsexport function await_waterfall(name, location) { if (DEV) { console.warn(%c[svelte] await_waterfall\n%cAn async derived, \${name}\ (${location}) was not read immediately after it resolved. This often indicates an unnecessary waterfall, which can slow down your app\nhttps://svelte.dev/e/await_waterfall, bold, normal); // ... } }即某个异步 derived 在解析后没有被立即读取往往说明存在不必要的瀑布式等待可能拖慢应用。该警告完整说明见 runtime-warnings 参考。另外AwaitExpression.js 中还有pickled_awaits的处理当await前面还有其他表达式、或后面跟着其他表达式时编译器会把await节点记入analysis.pickled_awaits并给表达式打上has_pickled_await标记以便转换阶段恢复正确的反应式上下文——这是模板中{a await b}这类混合表达式的底层支持。指示加载态pending 片段、$effect.pending 与 settled()要渲染占位 UI可以把内容包进带pending片段的svelte:boundary。文档svelte-boundary.md对其行为有明确界定{#snippet pending()} !-- 首次创建时显示后续更新不再显示 -- {/snippet}pending片段只在边界首次创建时显示对于后续异步更新它们是全局协调的改用$effect.pending()。典型场景是在表单字段旁显示正在异步校验你的输入的 spinner。从源码看$effect.pending()由边界块实现boundary.js 中#effect_pending是一个订阅自#local_pending_count的响应式源当计数发生变化时才通过internal_set更新且带effect_pending_outside_reaction的错误约束见 errors.js——即它必须在反应式上下文中使用。如果需要在一段代码里等当前更新彻底完成可以使用settled()它返回一个在当前更新完成时解析的 promise。文档给出的例子import { tick, settled } from svelte; async function onclick() { updating true; // 没有这一步的话updating 的变化会 // 与其他变化归入同一批 // 不会先反映到 UI 上 await tick(); color octarine; answer 42; await settled(); // 此时受 color 或 answer // 影响的更新已全部应用完毕 updating false; }例中color red; answer -1; updating false;为三个状态声明。settled()的运行时实现非常简洁见 runtime.js/** * Returns a promise that resolves once any state changes, * and asynchronous work resulting from them, have resolved * and the DOM has been updated * returns {Promisevoid} * since 5.36 */ export function settled() { return Batch.ensure().settled(); }即settled()就是当前批次含其触发的异步工作完成后 resolve这也解释了为什么先await tick()再改状态、最后await settled()能保证 spinner 的显示与隐藏各自独立成帧。错误处理await表达式中的错误会冒泡到最近的错误边界error boundary即最近的svelte:boundary。这意味着异步逻辑不需要在每个await处手写 try/catch边界组件统一接管失败渲染即可。服务端渲染SSRSvelte 通过render(...)API 支持异步 SSRrender(...)本身返回 promise直接await即可/// file: server.js import { render } from svelte/server; import App from ./App.svelte; const { head, body } await render(App);如果使用 SvelteKit 这类框架这一 await 由框架代劳。SSR 场景下await的具体行为规则见原文档若 SSR 时遇到带pending片段的svelte:boundary则渲染该pending片段边界内其余内容被忽略边界之外或无pending片段的边界内遇到的所有await表达式都会在await render(...)返回之前解析并渲染出内容。原文档还注明未来计划加入流式streaming实现让内容在后台逐步渲染。Forking投机执行与预加载fork(...)API 在 5.42 中加入使得你预期很快会发生的await表达式可以提前运行。它主要面向 SvelteKit 这类框架当用户表现出导航意图如 hover、focus 一个链接时先行预加载数据从而让真正的导航零等待。文档给出的完整示例——在按钮被 focus/hover 时预开菜单指针离开则丢弃点击时提交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; // 以防 pending 不存在 // 若存在这句是 no-op open true; }} open menu/button {#if open} !-- 该组件内部的任何异步工作 会在 fork 创建时就开始执行 -- Menu onclose{() open false} / {/if}Fork对象提供commit()异步把投机状态正式应用到 UI与discard()回滚并回收两个操作。源码实现见 batch.js其注释精确描述了语义/** * Creates a fork, in which state changes are evaluated * but not applied to the DOM. * ... * The fn parameter is a synchronous function that * modifies some state. The state changes will be reverted * after the fork is initialised, then reapplied if and * when the fork is eventually committed. * * When it becomes clear that a fork will _not_ be committed * (e.g. because the user navigated elsewhere), it must be * discarded to avoid leaking memory. * since 5.42 */几个值得注意的实现细节入口校验fork未开启async_mode_flag时直接抛experimental_async_required错误——即它和experimental.async是绑定的时机约束fork不能在批次batch执行期间调用否则报fork_timing错误commit 流程把投机期间记录的source.v重新写回并递增 write version主动 flush 受影响的$state.eager效果再batch.flush()并await settled若对已discard的 fork 调用commit会报fork_discarded错误discard 流程为 fork 中变化过的 source 递增 write version让可能变脏的 derived 有机会重算然后丢弃批次。因此使用fork的关键纪律是确定不会提交时务必discard()否则可能泄漏内存源码注释原话。注意事项与破坏性变更注意事项作为实验特性await的处理细节以及$effect.pending()等相关 API有可能在 semver 主版本之外发生破坏性变更官方表示会尽量把这类变更压到最小。Effect 执行顺序的破坏性变更当experimental.async为true时效果的运行顺序会略有不同——{#if ...}、{#each ...}等块级效果现在会先于同一组件中的$effect.pre或beforeUpdate执行。文档指出在极少见情况下在 effect 内部更新状态、且更新了一个本应不再存在的块可能出现异常因此应避免在 effect 中更新状态参见 $effect 文档何时不使用 $effect 一节。小结一张能力对照表需求使用的手段关键行为顶层/derived/模板中使用 awaitexperimental.async: true未开启或非 runes 模式会编译报错避免中间态闪烁依赖驱动的 await 表达式输入与结果同批更新快速更新可覆盖慢速更新并行异步工作模板中独立await表达式并行执行$derived间独立更新首次加载占位 UIsvelte:boundarypending片段仅首次创建时显示SSR 时只渲染 pending后续更新的 loading 指示$effect.pending()需在反应式上下文使用等待更新彻底完成settled()5.36返回当前批次完成 promise基于Batch.ensure()错误兜底最近的svelte:boundary错误向上冒泡至错误边界异步 SSRawait render(App)边界外 await 全部解析后返回预加载/投机执行fork(...)5.42commit()应用、discard()回收勿泄漏所有行为均可在仓库中对应验证编译器侧见 AwaitExpression.js校验与 flags/index.js模式标志运行时侧见 batch.jsfork与批次调度、runtime.jssettled、boundary.js$effect.pending计数与 warnings.jsawait_waterfall。【免费下载链接】svelteweb development for the rest of us项目地址: https://gitcode.com/GitHub_Trending/sv/svelte创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

uBlock Origin 源码仓库深度解析:从安装矩阵、默认过滤清单到静态网络过滤引擎与构建体系

uBlock Origin 源码仓库深度解析:从安装矩阵、默认过滤清单到静态网络过滤引擎与构建体系

uBlock Origin 源码仓库深度解析:从安装矩阵、默认过滤清单到静态网络过滤引擎与构建体系 【免费下载链接】uBlock uBlock Origin - An efficient blocker for Chromium and Firefox. Fast and lean. 项目地址: https://gitcode.com/GitHub_Trending/ub/uBlock …

2026/11/5 6:31:56 阅读更多 →
Strapi 数据迁移 WebSocket 协议解析:Remote Data Transfer 的 Dispatcher 消息模型与传输生命周期

Strapi 数据迁移 WebSocket 协议解析:Remote Data Transfer 的 Dispatcher 消息模型与传输生命周期

Strapi 数据迁移 WebSocket 协议解析:Remote Data Transfer 的 Dispatcher 消息模型与传输生命周期 【免费下载链接】strapi 🚀 Strapi is the leading open-source headless CMS. It’s 100% JavaScript/TypeScript, fully customizable, and developer…

2026/11/5 6:31:56 阅读更多 →
OpenClaw搬出Docker沙箱:AI Agent如何真正接管服务器运维

OpenClaw搬出Docker沙箱:AI Agent如何真正接管服务器运维

把OpenClaw当成服务器运维管家来用,念头很诱人;可真把它塞进Docker沙箱里跑几天,你会发现管家被关在了一个只能透过小窗口喊话的隔间里。Docker让部署变得干净整洁,同时也让AI能摸到的系统接口少得可怜——进程看不全、systemd够不…

2026/10/24 21:11:21 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/11/4 1:35:24 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/11/5 1:08:50 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/11/3 11:35:49 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/11/4 6:23:50 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/11/5 0:54:28 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/11/4 19:19:54 阅读更多 →

日新闻

周新闻

月新闻