在线直播,立即回复

杰夫·波斯尼克
Jeff Posnick

使用过 Service Worker 的任何人都可以告诉您,它们一直是异步的。它们仅依赖于基于事件的接口(如 FetchEvent),并使用 promise 在异步操作完成时发出信号。

当涉及 Service Worker 的提取事件处理脚本提供的响应时,异步性同样重要,但对开发者而言不太明显。流式响应是这里的黄金标准:它们允许发出原始请求的页面在第一个数据块可用后立即开始使用响应,并可能会使用针对流式处理进行了优化的解析器逐步显示内容。

在编写您自己的 fetch 事件处理脚本时,通常只向 respondWith() 方法传递您通过 fetch()caches.match() 获取的 Response(或 Response 的 promise),然后一天内调用即可。好消息是,通过这两种方法创建的 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() 收集其他数据,页面就会继续从该数据流中读取数据。从 Service Worker 流向页面的响应是真正异步的,并且我们可以完全控制流的填充情况!

实际应用

您可能已经注意到,前面的示例包含一些占位符 /* your data here */ 注释,并且未说明实际的实现细节。那么,现实世界会是什么样的示例呢?

Jake Archibald(并不奇怪!)有一个很好的例子,它使用数据流将来自多个缓存 HTML 代码段的 HTML 响应,以及通过 fetch() 流式传输的“实时”数据(在本示例中是他的博客的内容)拼接在一起

正如 Jake 所解释的那样,使用流式传输响应的优势在于,浏览器可以在 HTML 流式传输后对其进行解析和渲染,包括从缓存中快速加载的初始位,而无需等待整个博客内容提取完成。这样可以充分利用浏览器的渐进式 HTML 呈现功能。也可以逐步呈现的其他资源(例如某些图片和视频格式)也可以从此方法中受益。

信息流?还是 App Shell?

有关使用 Service Worker 为 Web 应用提供支持的现有最佳实践侧重于 App Shell + 动态内容模型。这种方法依赖于主动缓存 Web 应用的“shell”(显示结构和布局所需的最少 HTML、JavaScript 和 CSS),然后通过客户端请求加载每个特定页面所需的动态内容。

流为其带来了 App Shell 模型的替代方案,在该模型中,当用户导航到新页面时,会有更完整的 HTML 响应流式传输到浏览器。流式响应可以使用缓存资源,因此即使处于离线状态,它仍然可以快速提供初始 HTML 块!但最终它们看起来更像是传统的服务器渲染的响应正文。例如,如果您的 Web 应用的某个内容管理系统通过将部分模板拼接在一起来服务器呈现 HTML,则该模型会直接转换为使用流式响应,其模板逻辑会复制到 Service Worker(而不是您的服务器)中。如以下视频所示,就该用例而言,流式响应的速度优势可能非常明显:

流式传输整个 HTML 响应的一个重要好处是,它为何是视频中的最快替代方案,即在初始导航请求期间呈现的 HTML 可充分利用浏览器的流式 HTML 解析器。在页面加载后插入文档(这在 App Shell 模型中很常见)的 HTML 块无法利用这种优化。

那么,如果您处于 Service Worker 实现的规划阶段,应该采用哪种模型:是逐步呈现的流式响应,还是轻量级 shell 结合客户端对动态内容的请求?答案不出意在于,这取决于:您的现有实现是否依赖 CMS 和部分模板(优势:数据流);您是否期望单个大型 HTML 载荷能够受益于渐进式呈现(优势:数据流);您的 Web 应用是否最适合建模为单页应用(优势:应用 Shell 的稳定模型)。

由 Service Worker 驱动的流式响应仍处于早期阶段,我们期待看到不同的模型日渐成熟,特别是看到更多工具开发出来来自动执行常见使用场景。

深入了解直播

如果您要构建自己的可读流,只是不加区分地调用 controller.enqueue() 可能不足够或高效。Jake 详细介绍了如何搭配使用 start()pull()cancel() 方法创建针对您的用例量身定制的数据流。

如果您想要了解更多详情,请参阅 Streams 规范

兼容性

Chrome 52 中添加了对使用 ReadableStream 作为其来源在 Service Worker 中构建 Response 对象的支持。

Firefox 的 Service Worker 实现尚不支持由 ReadableStream 支持的响应,但有一个相关的跟踪 bug,可以跟踪 Streams API 支持。

您可以在 Microsoft 的“平台状态”页面跟踪 Edge 中无前缀 Streams API 支持的进度,以及总体 Service Worker 支持