任何使用过服务工件的开发者都会告诉您,服务工件始终是异步的。它们完全依赖于基于事件的接口(例如 FetchEvent
),并使用promise 在异步操作完成时发出信号。
异步性同样重要,尽管当涉及到 Service Worker 的提取事件处理程序提供的响应时,它对开发者的可见性较低。在这种情况下,流式响应是黄金标准:它们允许发出原始请求的网页在第一部分数据可用后立即开始处理响应,并且可能会使用针对流式传输进行了优化的解析器逐渐显示内容。
编写自己的 fetch
事件处理脚本时,通常只需将您通过 fetch()
或 caches.match()
获取的 Response
(或 Response
的 Promise)传递给 respondWith()
方法,然后就大功告成了。好消息是,这两种方法创建的 Response
已经可以流式传输了!坏消息是,“手动”构建的 Response
无法流式传输,至少目前是如此。这时,Streams API 就派上用场了。
数据流?
流是一种可以以增量方式创建和操作的数据源,它提供了一个用于读取或写入异步数据块的接口,在任何给定时间,内存中可能只有其中一部分数据可用。目前,我们关注的是 ReadableStream
,它可用于构建传递给 fetchEvent.respondWith()
的 Response
对象:
self.addEventListener('fetch', event => {
var stream = new ReadableStream({
start(controller) {
if (/* there's more data */) {
controller.enqueue(/* your data here */);
} else {
controller.close();
}
});
});
var response = new Response(stream, {
headers: {'content-type': /* your content-type here */}
});
event.respondWith(response);
});
调用 event.respondWith()
后,请求触发了 fetch
事件的页面会立即收到流式响应,并且只要 Service Worker 继续对其他数据执行 enqueue()
操作,该页面就会继续从该数据流中读取数据。从服务工作器流向网页的响应是真正异步的,并且我们可以完全控制填充数据流!
实际用途
您可能已经注意到,上一个示例中有一些占位符 /* your data here */
注释,并且缺少实际实现细节。那么,真实示例会是怎样的?
Jake Archibald(毫不奇怪!)提供了一个绝佳示例,展示了如何使用流将来自多个缓存的 HTML 代码段的 HTML 响应以及通过 fetch()
流式传输的“实时”数据拼接在一起,在本例中,这些数据是其博客的内容
正如 Jake 所说的,使用流式响应的优势在于,浏览器可以在 HTML 内容传入时解析和呈现 HTML(包括从缓存快速加载的初始位),而不必等待整个博客内容提取完成。这样可充分利用浏览器的渐进式 HTML 呈现功能。其他可渐进呈现的资源(例如某些图片和视频格式)也可以从这种方法中受益。
数据流?或者应用壳?
关于使用服务工作线程为 Web 应用提供支持的现有最佳实践侧重于 App Shell + 动态内容模型。这种方法依赖于主动缓存 Web 应用的“外壳”(显示结构和布局所需的最少 HTML、JavaScript 和 CSS),然后通过客户端请求加载每个特定网页所需的动态内容。
流式传输为应用壳模型提供了一种替代方案,在这种方案中,当用户导航到新页面时,系统会向浏览器流式传输更完整的 HTML 响应。流式响应可以使用缓存的资源,因此即使在离线状态下,也仍然可以快速提供初始 HTML 块!不过,它们最终看起来更像传统的服务器呈现的响应正文。例如,如果您的 Web 应用由内容管理系统提供支持,该系统通过拼接部分模板来服务器渲染 HTML,那么该模型会直接转换为使用流式响应,模板逻辑会复制到服务工件中,而不是服务器中。如以下视频所示,对于该用例,流式响应提供的速度优势可能非常显著:
流式传输整个 HTML 响应的一个重要优势(这也是为什么它是视频中速度最快的替代方案)是,在初始导航请求期间呈现的 HTML 可以充分利用浏览器的流式 HTML 解析器。在网页加载后插入文档中的 HTML 代码段(在应用壳模型中很常见)无法利用此优化。
因此,如果您正处于服务工件实现的规划阶段,应该采用哪种模型:逐渐呈现的流式响应,还是轻量级 shell 与动态内容的客户端请求相结合?答案并不令人意外,具体取决于:您是否有依赖于 CMS 和部分模板的现有实现(优势:流式传输);您是否希望使用单个大型 HTML 载荷,并希望该载荷受益于渐进式呈现(优势:流式传输);您的 Web 应用是否最适合采用单页应用的模型(优势:App Shell);以及您是否需要目前受多种浏览器稳定版支持的模型(优势:App Shell)。
由 Service Worker 提供支持的流式响应仍处于早期阶段,我们期待看到不同的模型成熟,尤其是看到更多用于自动执行常见用例的工具。
深入了解数据流
如果您要构建自己的可读流,直接随意调用 controller.enqueue()
可能不够有效或不够高效。Jake 详细介绍了如何将 start()
、pull()
和 cancel()
方法搭配使用,以创建适合您的用例的数据流。
如果您想了解更多详情,请参阅数据流规范。
兼容性
Chrome 52 中添加了使用 ReadableStream
作为来源在服务工作器内构建 Response
对象的支持。
Firefox 的 Service Worker 实现尚不支持由 ReadableStream
支持的响应,但您可以关注一个相关的 Streams API 支持跟踪 bug。
如需跟踪 Edge 中无前缀 Streams API 支持以及整体服务工件支持的进度,请访问 Microsoft 的平台状态页面。