DEBUG.WIKI
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国

Svelte pending 一直不结束

同时含脚本与模板 await 的子组件提前销毁,旧 Svelte 续行可能抛 async_deriveds 并留住 pending。核对 5.57.2 计数清理修复。

异步子组件已经从页面移除,外层 <svelte:boundary> 却仍显示 loading。等子组件里的 promise 完成,控制台又报:

TypeError: Cannot read properties of null (reading 'async_deriveds')

在启用 experimental.async 的 Svelte 5.57.1 中,如果子组件同时有脚本顶层 await 和模板 await,并在前者完成之前被销毁,可能进入这条续行缺口。正式版 5.57.2 修正了对应的销毁判断和 pending 计数释放。

两处 await 与提前销毁要同时存在

子组件可以有这样的结构:

<script>
  const value = await loadValue();
</script>

<p>{await Promise.resolve(value)}</p>

loadValue 是应用自己的异步加载函数,需要在原组件中正确提供。这里只展示两个 await 的关系,不是完整的数据请求程序。

外层条件分支先挂载组件,又在 loadValue 尚未完成时把它移除。随后脚本 promise 完成,模板 await 的续行仍尝试创建异步派生值,便可能触发空 batch 错误。

普通模板 {value}、只有脚本里的 $derived(await ...),或只有模板 await 而没有脚本顶层 await,不是同一组合。这个缺陷在开发与生产路径都可能出现,并不要求一定存在 boundary;没有边界时,还可能使根节点一直不渲染。

抛错之前漏掉了计数清理

模板的异步表达式需要先等待脚本 blockers。旧 flatten 的回调在 blockers 完成后恢复上下文,再启动这些表达式,却没有先确认父 effect 已经销毁。

已销毁 effect 不会重新激活 batch,后续创建 async derived 时就访问到空值。异常发生得太早,正常用于减少 pending 的 finally 也没有接上,于是即使目标子组件已经消失,边界仍残留它的等待计数。

只捕获或隐藏 TypeError,不能补上这次计数释放。把所有子组件一直保留在页面里,也会改变业务生命周期,并不适合作为通用处理。

正式修复在续行前检查父组件

5.57.2 在 blocker 完成的回调中先检查父 effect 的销毁状态。如果已经销毁,就减少这次 pending 并返回,不再创建该组件的异步派生值。仍存活的组件继续走正常流程。

项目应选择兼容的修复版本,重新生成实际客户端资源,保留原来的显示与卸载条件。不能因为不再显示 loading,就关闭所有异步边界或取消必要的数据验证。

应用本身还要按业务需要处理已经发出的异步工作。框架停止销毁后的渲染续行,不表示外部请求自动取消,也不表示此前可能发生的副作用被撤销。

保留晚完成的条件验收

在受控环境中,让加载持续到组件卸载之后,再使其完成,确认没有 async_deriveds 空值错误,目标组件对应的 pending 不再残留。若边界里还有其他真正未完成的 await,它们仍应按正常规则保持等待。

随后验证不卸载时组件能显示正确数据,以及后续页面更新仍然工作。只看某个 loading 消失,无法区分计数正确释放、组件被强制移走或错误被隐藏。