04|异步组件与流式输出
第八章 / 04。目标:区分首字节、首段可读 HTML、异步边界完成和客户端可交互。
字符串渲染与流式渲染
renderToString(app) 等整棵树就绪后返回字符串;流式 API 可以逐段把 HTML 写给宿主。Node ESM 环境优先 pipeToNodeWritable,Web 平台可用 renderToWebStream/pipeToWebWritable,跨宿主实验可用 renderToSimpleStream。
流式输出可能改善内容到达顺序,但不会自动缩短数据查询,也不会让客户端代码更早可交互。代理缓冲、压缩、CDN 与浏览器解析都可能改变 chunk 的实际到达。
可重放 chunk 实验
在第一章 Vue 实验项目根新建 stream-lab.mjs,以下代码完整运行于 Node,执行 node stream-lab.mjs。它不需要浏览器或 Nuxt。
import { createSSRApp, defineComponent, h } from 'vue'
import { renderToSimpleStream } from 'vue/server-renderer'
const AsyncPart = defineComponent({
async setup() {
await new Promise(resolve => setTimeout(resolve, 30))
return () => h('strong', 'async ready')
}
})
const app = createSSRApp({
render: () => h('main', [h('h1', 'shell'), h(AsyncPart)])
})
const started = performance.now()
const chunks = []
await new Promise((resolve, reject) => {
renderToSimpleStream(app, {}, {
push(chunk) {
chunks.push({ at: performance.now() - started, chunk })
if (chunk === null) resolve()
},
destroy: reject
})
})
console.table(chunks.map(x => ({ at: x.at, bytes: x.chunk?.length ?? 0 })))30ms 只是制造顺序的实验输入。不同 Vue 版本和组件结构可能合并 chunk,不应断言固定 chunk 数;断言最终拼接 HTML 包含 shell 与 async ready,并记录每个实际 push 时间。
组件失败不等于流被销毁
async setup 的拒绝先交给 Vue 组件错误处理链。当前 Vue 3.5 可以把这个组件渲染成注释或 fallback 后继续输出,最终正常调用 push(null);因此只监听 destroy 会漏掉业务渲染错误。onErrorCaptured 可以局部处理并返回 false 阻止继续上报,app.config.errorHandler 接收未被局部处理的组件异常;流目标的 destroy(error) 则是另一条失败通道。
本节练习把 ended 定义为“应用渲染成功完成”,需要同时收集两条通道;不是把流写完等同于请求成功。错误已由组件局部恢复时可继续成功,但必须提供产品允许的 fallback,不能无声丢掉关键正文。
响应提交后的错误合同
流式响应一旦发送状态行/headers,后续异步失败通常不能再改成完整 500 页面。服务器必须:
- 在提交前完成鉴权和关键参数校验;
- 为可恢复异步区提供边界内 fallback;
- 流开始后若失败,终止/结束流并关联 request id 上报;
- 客户端对不完整内容提供重试,而不是静默显示半页。
把所有数据都 await 完再开流可简化错误处理,却失去早发 shell 的机会;哪些依赖阻塞首段是产品决策。Nuxt 的 useAsyncData 默认会参与页面 Suspense;lazy 让客户端导航不阻塞,但页面必须根据 status/error 明确渲染状态。
练习:记录成功与失败流
封装 collectStream(app);app 必须每次新建、没有已有 errorHandler。收集未被局部错误边界处理的组件异常与流异常,返回 { chunks, html, ended, error }。成功组件用可控 Promise 延迟,断言最终 HTML 同时包含 shell 与正文;失败组件 reject,断言 error 保留原始 Error 身份且 ended 不被误报为正常完成。不得断言固定 chunk 数或固定毫秒。
参考答案:第 8 章第 04 题。