简体   繁体   中英

Worker using synchronous XMLHttpRequest to get data from GUI

I'd like a Web Worker which is deep in a call stack to be able to make a synchronous request to get information from the GUI.

The GUI itself is not blocked--it's able to process messages. But the JavaScript on the worker's stack is not written in async / await style. It is just a lot of synchronous code. So if the GUI tried to send a response back to the worker with a postMessage, that would just be stuck in the onmessage() queue.

I've found at least one hack that works in today's browsers. The worker can postMessage to the GUI for the information it wants--along with some sort of ID (eg a UUID). Then it can make a synchronous XMLHttpRequest-- which is not deprecated on workers --to some server out on the web with that ID.

While the worker is waiting on that http request, the GUI processes the information request. When it's done, it does an XMLHttpRequest to POST to that same server with the ID and the data. The server then uses that information to fulfill the blocking request it is holding open for the worker. This fulfills the synchronous request.

It may seem hare-brained to outsource synchronization between the GUI and the worker to a server. But I'll do it if I have to, because it does not fit the use case to force the worker code to be written in asynchronous style. Also, I'm assuming that someday the browser will be able to do this kind of synchronization natively. But it looks like the one mechanism which could have been used-- SharedArrayBuffer , has been disabled for the time being.

UPDATE circa late 2018: SharedArrayBuffer was re-enabled in Chrome for desktop v67 . It's not back on for Android Chrome or other browsers yet , and might be a while.

(More bizarre options like compiling a JavaScript interpreter into the worker so the JS stack could be suspended and restarted at will are not on the table--not just due to size and performance, but the inability to debug the worker using the browser's developer tools.)

So...

  • Is there any way for a synchronous XMLHttpRequest to be fooled into making a request of something coming from within the browser itself (maybe via a custom link scheme?) If the GUI thread could directly answer an XMLHttpRequest that would cut out the middleman.

  • Could the same functionality be provided via some kind of plugin? I'm thinking maybe synchronization could be done as an abstraction. If someone doesn't have the plugin, it falls back to using the network as a synchronization surrogate. (And presumably if they ever re-enable SharedArrayBuffer, it could just use that.)

I'm wondering also if there is some kind stock JS-ready service which already implements the protocol for the echo server...if anyone knows of one. Seems quite easy to write.

I don't see a way to do what you're trying to do. Approaches that appear initially promising eventually run into hard problems.

Service Workers and fetch

In a comment, you suggested service workers as a possible solution. The service workers examples I've seen mention providing "custom responses to requests". However, all examples use the fetch event to provide the custom response. AFAIK, it is produced only when you actually use the fetch API specifically. An xhr won't generate a fetch event. (Yes, I've tried it and it did not work.) And you cannot just use fetch in your specific situation instead of xhr because fetch does not operate synchronously. The specs for fetch mention a "synchronous flag", but it is not part of the API .

Note that the fetch API and the associated event are not specific to service workers so you could use fetch in a plain worker, or elsewhere, if it solved your problem. You often see fetch mentioned with service workers because service workers can be used for scenarios where regular workers cannot be used and some of those scenarios entail providing custom responses to fetch requests.

Fake XMLHttpRequest

Marinos An suggested in a comment using a fake XMLHttpRequest object. In most cases , that would work. Testing frameworks like Sinon provide fake XMLHttpRequest that allow testing code to have complete control over the responses that the code under test gets. However, it does not work for your use-case scenario. If your fake xhr implementation is implemented as one JavaScript object, and you try sending it to the worker, the worker will get a complete clone of it. Actions on the fake xhr performed inside the worker won't be seen outside the worker. Actions on the fake xhr performed outside the worker won't be seen inside the worker.

It is theoretically possible to work around the cloning issue by having the fake xhr consist of two objects: a front end through which requests are performed, and a backend through which fake responses are established. You could send the front end to the worker, but the front end and the back end would have to communicate with each other and this brings you right back to the communication problem you were trying to solve. If you could make the two parts of the fake xhr talk to each other in a way that allows you to fake synchronous xhr requests, then by the same token you would be able to solve the communication problem without the fake xhr.

Hm...perhaps you could create your workers on the fly, like so

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

And then, for each new http request you need, you start its own worker:

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

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

Both will be asynchronous and will not block the interface for you user, and they both will be able to do their own work, and each of them will have its own listeners.

Notice that code is a string , so, if you have a function, you can use it .toString() concatenated with () , like this:

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

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

This way, when running your worker, it will create and execute the function instantly inside each worker of yours.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM