Partytown 的同步通信功能如何运作
我们最近发布了 Partytown 的 alpha 版本,这是一个可以帮助你将第三方脚本迁移到 Web Worker 中的库,这样主线程就可以专门用于运行你的代码。想了解网站如何从中受益,我建议你阅读:Partytown 简介 🎉:从 Web Worker 运行第三方脚本。本文主要面向好奇的读者,旨在讲解 Partytown 的工作原理。
异步 postMessage()
多年来,人们一直鼓励将耗时且资源密集型的任务移至Web Worker中执行。然而,一个重要的限制是主线程和 Web Worker之间的通信必须是异步的。这意味着一个线程发送的消息不会等待另一个线程接收,也不会等待其返回值。
从本质上讲,postMessage()方法就是一个“发送后即弃”的方法。这完全没问题,可以围绕它构建一个通信层,postMessage()这样你的代码就可以使用 Promise、async/await 甚至回调函数(所有这些都是异步的)。
Comlink是一个非常棒的项目,可以帮助你轻松使用 Web Workers ,它“……消除了你对 postMessage 的思考障碍,并隐藏了你正在使用 Workers 的事实。” Comlink 的确很出色,但截至撰写本文时,你仍然会遇到一个障碍:主线程和 Web Worker 之间的调用必须是异步的。
为什么第三方脚本不能使用 postMessage()
正如我在第一篇文章中所述,实际上你不能简单地重构第三方脚本。它们托管在另一个域中,由另一个服务控制,并且是为了处理无数种场景而设计的,以便在全球数十亿台不同的设备上执行。
然而,许多脚本(例如 Google Analytics)只是收集信息,然后使用`navigator.sendBeacon() ` 将数据发布到它们的服务器。这是最佳情况,因为 Google Analytics 实际上只是一个后台任务。它可以按照自己的计划运行,并在另一个线程中缓慢地收集和发布数据。
然而,问题在于仍然存在一些阻塞式 API 调用,这些 API 在 Web Worker 中不可用。例如,document.title` window.screen.widthget_ document...window
由于第三方脚本必须保持不变,并且 Web Worker必须进行异步通信,因此我们一直处于这种停滞状态,大部分性能问题无法转移到另一个线程中。
用这个奇特的同步技巧
这才是真正有趣的部分!🧑🔬 让我们来了解一下这个鲜为人知的 API:同步 XMLHttpRequest。在如今的 Web 开发中,它并不为人所熟知,原因显而易见。但在过去,当我们还在使用 Adobe Flash、Java 小程序和 Dreamweaver 编写 DHTML 的时候,这种情况非常普遍:
var request = new XMLHttpRequest();
request.open('GET', '/data.xml', false); // `false` makes the request synchronous
request.send(null);
问题在于,在主线程中,这个阻塞调用会导致网页卡死,直到收到响应。根据MDN 的说法:
同步 XHR 现已被弃用。建议开发者放弃使用同步 API,转而使用异步请求。
在当今的 Web 开发环境中,最好使用更现代的fetch() API。
然而,Web Worker 的同步执行能力是许多工具的核心。最主要的是`importScripts()`,但同步 XHR 也属于此类。这些同步 API在Web Worker内部并未被标记为已弃用。在 GitHub 上快速搜索 `importScripts()`即可看出它们的使用范围之广。但再次强调,它仅在 Web Worker 中可用!
所以,结果证明……我们可以让 Web Worker 阻塞……🧐
拦截同步请求
当代码仅在工作线程内部执行时,我们可以发出同步 HTTP 请求,这实际上会阻塞 Web Worker 线程的执行,直到收到 HTTP 响应。利用这种能力(以及我们“疯狂科学家”般的思维),我们可以将 Web Worker 代码阻塞执行,然后使用 HTTP 请求异步调用相应的函数postMessage()。请记住,HTTP 请求和响应是异步的。因此,尽管 Web Worker 线程可能“认为”它是同步的,但我们可以拦截实际的 HTTP 请求并获得异步响应。
这就引出了另一个奇招。医生们最讨厌它了!
Service Worker 能够通过onfetch拦截请求。这意味着 Web Worker 发出的请求也可以被我们自己的代码拦截和处理。这些请求并非外部请求,不会访问实际的网络,而是在 Service Worker 内部本地处理。onfetch之后,我们可以在 Service Worker 内部使用 onfetchpostMessage()来进行真正的异步通信。
Service Worker 目前还无法直接访问主线程。但由于我们现在采用的是异步通信,因此我们可以从 Service Worker 内部利用其postMessage()自身与主线程通信,并让主线程将消息发送回 Service Worker。然后,Service Worker 会完成它之前拦截到的 HTTP 响应。
因此,我们仍然面临异步约束,但结合同步 XHR 和请求拦截,我们可以有效地将异步调用转换为阻塞调用。接下来,我们通过使用JavaScript 代理封装所有主线程的访问,使其更易于使用。
此外,Partytown 应该仍然可以在旧版浏览器上运行。它的初始化机制之一是,如果浏览器不支持 Service Worker,那么它基本上就是以传统方式运行第三方脚本(也就是我们现在都在使用的方式)。
原子能呢?
太好了,很高兴你问到这个问题。就我个人而言,我希望从长远来看, Atomics能成为解决方案。由于优秀的开源社区提出了许多绝妙的想法,我们已经取得了进展,提供了两种构建版本:Atomics 和 Service Worker。库运行时,会根据浏览器的支持情况来决定使用哪一个。
目前,计划是最终将 Service Worker 技巧作为备选方案,但 Atomics 还有很多研究要做。对于 Atomics 的未来来说,好消息是 Safari 技术预览版刚刚启用了 SharedArrayBuffer!
接下来会发生什么?
Partytown 目前仍处于 alpha 测试阶段,每次提交都会进行很多更改😬。但我们已经在Builder.io 的几个页面上积极运行它,以便收集更多生产数据。
此外,我们正在与一些大量使用第三方脚本的电商网站合作,看看能否帮助他们提升性能和用户体验。我们非常欢迎您加入我们的Discord 频道,畅谈想法,或者参与测试并在我们的GitHub 代码库中提交问题!
所以请继续关注我们的后续实验。在后续文章中,我们将继续深入挖掘图书馆的其他部分,随着我们收集到更多数据,我们希望能够提供一些确凿的数据,来展示 Partytown 的优势。
继续狂欢吧,加思!
文章来源:https://dev.to/adamdbradley/how-partytown-s-sync-communication-works-4244