在线直播,立即回复

任何使用过服务工件的开发者都会告诉您,服务工件始终是异步的。它们完全依赖于基于事件的接口(例如 FetchEvent),并使用promise 在异步操作完成时发出信号。

对于服务工件的提取事件处理脚本提供的响应,异步同样重要,但对开发者而言,异步的显现性较低。在这种情况下,流式响应是黄金标准:它们允许发出原始请求的网页在第一部分数据可用后立即开始处理响应,并且可能会使用针对流式传输进行了优化的解析器逐渐显示内容。

编写自己的 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);
});

请求触发 fetch 事件的网页会在调用 event.respondWith() 后立即收到流式响应,并且只要服务工作器继续 enqueue() 其他数据,该网页就会继续从该数据流中读取。从服务工件流向网页的响应是真正异步的,并且我们可以完全控制填充数据流!

实际用途

您可能已经注意到,前面的示例中有一些占位符 /* your data here */ 注释,并且缺少实际实现细节。那么,真实示例会是怎样的?

Jake Archibald(毫不奇怪!)提供了一个绝佳示例,展示了如何使用流将来自多个缓存的 HTML 代码段的 HTML 响应以及通过 fetch() 流式传输的“实时”数据拼接在一起,在本例中,就是他的博客内容

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

数据流?或者应用壳?

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

流式传输为应用壳模型提供了一种替代方案,在这种方案中,当用户导航到新页面时,系统会向浏览器流式传输更完整的 HTML 响应。流式响应可以使用缓存的资源,因此即使在离线状态下,也仍然可以快速提供初始 HTML 块!不过,它们最终看起来更像传统的服务器呈现的响应正文。例如,如果您的 Web 应用由内容管理系统提供支持,该系统通过拼接部分模板来服务器渲染 HTML,那么该模型会直接转换为使用流式响应,模板逻辑会复制到服务工件中,而不是服务器中。如以下视频所示,对于该用例,流式响应带来的速度优势可能非常显著:

流式传输整个 HTML 响应的一个重要优势(这也是为什么它是视频中速度最快的替代方案)是,在初始导航请求期间呈现的 HTML 可以充分利用浏览器的流式 HTML 解析器。在网页加载后插入文档中的 HTML 代码段(在应用壳模型中很常见)无法利用此优化。

因此,如果您正处于服务工件实现的规划阶段,应该采用哪种模型:逐渐呈现的流式响应,还是轻量级 shell 与动态内容的客户端请求相结合?答案并不令人意外,具体取决于:您是否有依赖于 CMS 和部分模板的现有实现(优势:流式传输);您是否希望使用单个大型 HTML 载荷,并希望该载荷受益于渐进式呈现(优势:流式传输);您的 Web 应用是否最适合采用单页应用的模型(优势:App Shell);以及您是否需要目前受多种浏览器稳定版支持的模型(优势:App Shell)。

服务工件支持的流式响应功能仍处于起步阶段,我们期待看到不同的模型不断成熟,尤其是期待看到开发出更多用于自动执行常见用例的工具。

深入了解数据流

如果您要构建自己的可读取数据流,仅仅无差别地调用 controller.enqueue() 可能不够或效率不高。Jake 详细介绍了如何将 start()pull()cancel() 方法搭配使用,以创建适合您的用例的数据流。

如果您想了解更多详情,请参阅数据流规范

兼容性

Chrome 52 中新增了使用 ReadableStream 作为来源在服务工件中构建 Response 对象的支持。

Firefox 的服务工件实现尚不支持由 ReadableStream 支持的响应,但您可以关注一个相关的 Streams API 支持跟踪 bug

您可以访问 Microsoft 的平台状态页面,跟踪 Edge 中无前缀 Streams API 支持以及整体 Service Worker 支持的进度。