【问题标题】:Worker using synchronous XMLHttpRequest to get data from GUIWorker 使用同步 XMLHttpRequest 从 GUI 获取数据
【发布时间】:2018-10-08 15:31:37
【问题描述】:

我想要一个位于调用堆栈深处的Web Worker,以便能够发出同步请求以从 GUI 获取信息。

GUI 本身没有被阻止——它能够处理消息。但是worker栈上的JavaScript不是用async / await风格写的。它只是很多同步代码。因此,如果 GUI 尝试使用 postMessage 将响应发送回工作人员,那只会卡在 onmessage() 队列中。

我发现了至少一种适用于当今浏览器的 hack。 工作人员可以将消息发布到 GUI 以获取所需的信息 - 以及某种 ID(例如 UUID)。然后它可以向网络上具有该 ID 的某个服务器发出同步 XMLHttpRequest--which is not deprecated on workers--。

当工作人员等待该 http 请求时,GUI 会处理信息请求。完成后,它会执行 XMLHttpRequest 以 POST 到具有 ID 和数据的同一服务器。然后,服务器使用该信息来满足它对工作人员开放的阻塞请求。这满足了同步请求。

将 GUI 和 worker 之间的同步外包给服务器似乎有点愚蠢。但如果必须,我会这样做,因为它不适合强制工作代码以异步方式编写的用例。另外,我假设总有一天 浏览器将能够在本地进行这种同步。但看起来可以使用的一种机制——SharedArrayBuffer,暂时被禁用了。

大约在 2018 年底更新: SharedArrayBuffer 是 re-enabled in Chrome for desktop v67Android Chrome or other browsers yet 没有恢复,可能需要一段时间。

(更多奇怪的选项,如 compiling a JavaScript interpreter into the worker,因此 JS 堆栈可以随意暂停和重新启动 - 不仅因为大小和性能,还因为无法使用浏览器的开发者工具。)

所以...

  • 有什么方法可以让同步 XMLHttpRequest 被愚弄,从浏览器本身发出请求(可能是通过自定义链接方案?)如果 GUI 线程可以直接回答 XMLHttpRequest 会切断退出中间人。

  • 是否可以通过某种插件提供相同的功能?我在想也许同步可以作为一种抽象来完成。如果有人没有插件,它会退回到使用网络作为同步代理。 (大概如果他们重新启用 SharedArrayBuffer,它就可以使用它。)

我也想知道是否有某种股票 JS-ready 服务已经实现了回显服务器的协议......如果有人知道的话。看起来很容易写。

【问题讨论】:

  • “有什么方法可以让同步的 XMLHttpRequest 被愚弄,从浏览器本身发出请求”。你的意思是模拟同步XMLHttpRequest?例如jsfiddle.net/07nvgrt8/17 ?
  • @MarinosAn 我没有看到像这样的模拟对 OP 试图做的事情起作用。问题是相同的模拟对象必须同时存在于启动worker的脚本和worker中。好的,所以您只需将模拟发送给工人,对吗?好吧,不,因为传递给工人的对象是克隆的。因此,在工作人员内部的模拟上提出的任何请求都不会被工作人员外部的模拟看到。
  • @Louis 有人建议我看看"service workers",这与普通工人不同,是supported in most browsers。我没有听说过他们,但我不知道他们是否会给桌子带来任何新东西——在我看来,他们充其量可能会允许一个人对工人做一个自旋锁(坏的)并使用Service Worker 进行中继。还没有完全理解它,希望lazyweb能帮助我。 :-)
  • @HostileFork 我看到的服务工作者示例提到提供“对请求的自定义响应”,但所有示例都使用fetch 事件来提供自定义响应。 AFAIK,它仅在您实际使用fetch API 专门时产生。 xhr 不会生成fetch 事件。而且您不能只在特定情况下使用fetch 而不是xhr,因为fetch 不会同步操作。规范中提到了“同步标志”,但它是not part of the API
  • @Louis 您的 cmets 帮助我避免浪费时间调查服务人员...所以如果您想在赏金截止日期之前将它们消化成“您不走运”的帖子 (也许你对我描述的 XMLHttpRequest 服务有任何见解,如果你碰巧有的话),如果这是不幸的答案,我会给你积分......!

标签: javascript xmlhttprequest synchronization web-worker


【解决方案1】:

我看不出有办法做你想做的事。最初看起来很有希望的方法最终会遇到难题。

服务工作者和fetch

在评论中,您建议服务工作者作为可能的解决方案。我见过的服务工作者示例提到提供“对请求的自定义响应”。但是,所有示例都使用fetch 事件来提供自定义响应。 AFAIK,只有在您实际使用 fetch API 时才会产生它。 xhr 不会生成 fetch 事件。 (是的,我已经尝试过了,但它没有用。)你不能只在你的特定情况下使用fetch 而不是 xhr,因为 fetch 不会同步操作。 fetch 的规范提到了“同步标志”,但它是 not part of the API

请注意,fetch API 和相关事件并非特定服务工作人员,因此您可以在普通工作人员或其他地方使用fetch,如果它解决了您的问题。您经常会看到 fetch 提到 Service Worker,因为 Service Worker 可用于无法使用常规 Worker 的场景,其中一些场景需要对 fetch 请求提供自定义响应。

伪造的 XMLHttpRequest

Marinos 在comment 中建议使用伪造的 XMLHttpRequest 对象。 在大多数情况下,这是可行的。像 Sinon 这样的测试框架提供了虚假的 XMLHttpRequest,允许测试代码完全控制被测代码获得的响应。但是,它不适用于您的用例场景。如果你的假 xhr 实现是作为一个 JavaScript 对象实现的,并且你尝试将它发送给 worker,worker 将获得它的完整克隆。在工作人员内部执行的对假 xhr 的操作不会在工作人员外部看到。在工作人员外部执行的对假 xhr 的操作不会在工作人员内部看到。

理论上可以通过让假 xhr 包含两个对象来解决克隆问题:一个用于执行请求的前端,一个用于建立假响应的后端。您可以将前端发送给工作人员,但前端和后端必须相互通信,这会让您回到您试图解决的通信问题。如果您可以让假 xhr 的两个部分以一种允许您伪造同步 xhr 请求的方式相互通信,那么出于同样的原因,您将能够在没有假xhr。

【讨论】:

  • 我提出服务工作者的原因是因为他们曾经使用过 XMLHttpRequest。 But this is no longer available, apparently.。我想我剩下的问题是可以用 Chrome/Firefox 插件、Flash 控件(!)或其他方式来弥补差距,直到 SharedArrayBuffer 或其他一些解决方案。在这一点上,这个问题可能需要接受一个新的答案,但看起来你现在可能是它!
  • 唉,关于插件或 Flash,我无话可说。在考虑插件或 Flash 之前,我愿意经历许多其他障碍,包括重写工作线程以异步工作。
  • 我正在解决与 OP 类似的问题。我的实验表明服务工作者确实响应同步 XHR,但前提是它们是从 Web 工作者发送的,而不是主 GUI 线程。当您说“一个 xhr 不会生成 fetch 事件。(是的,我已经尝试过但它没有工作。)”我怀疑您要么尝试了从主线程发送的请求,要么浏览器最近改变了一些事情.
  • 那是三年半前的事了。我不太记得我是如何测试它的,是的,浏览器可能已经改变了它们对这个特定问题的操作方式。
【解决方案2】:

嗯...也许您可以像这样即时创建您的工人

function startNewWorker (code) {
  var blob = new Blob([code], {type: "application/javascript"});
  var worker = new Worker(URL.createObjectURL(blob));
}

然后,对于您需要的每个新的 http 请求,您启动它自己的工作器:

const w1 = startNewWorker(yourCodeThatDoesSomething);
w1.onmessage = function () { /* ... */};

const w2 = startNewWorker(yourCodeThatDoesSomething);
w2.onmessage = function () { /* ... */};

两者都是异步的,不会阻塞用户界面,它们都可以做自己的工作,并且每个人都有自己的监听器。

注意code是一个字符串,所以,如果你有一个函数,你可以使用.toString()()连接,像这样:

function myWorkerContent () {
  // do something ....
}

const code = "(" + myWorkerContent.toString() + ")()";
// or, if you want to use templateLiterals
// const code = `(${myWorkerContent.toString()})()`;

这样,在运行你的worker时,它会立即在你的每个worker中创建并执行函数。

【讨论】:

  • 正如问题中提到的,我没有遇到 GUI 被阻止的问题。问题是工作人员需要来自 GUI 的信息,而不必展开其堆栈并“屈服”到其 onmessage 循环以获取该信息。产生另一个工人并不能真正解决问题,除非它可以在被阻塞时使用某种特权模式的工人对工人通信(我在这里寻找可能可以回答同步 XMLHttpRequest 的东西,但看起来没有骰子)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-07
  • 2015-12-04
  • 2011-10-27
  • 2022-01-25
  • 2012-01-29
相关资源
最近更新 更多