-
Notifications
You must be signed in to change notification settings - Fork 390
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Web Worker Hangs if Imported File Contains Top-Level Await
#635
Comments
I had the exact same issue. I think that's because of how |
Right, so currently it’s the user’s responsibility to make sure the worker is ready before using Comlink. Because you create the worker in the click hander (side-note: generally I wouldn’t recommend that!), you call You either need to send a ”ready!” message and wait for that first or — the more fragile, hackier and easier solution — is to move the worker creation to the top of the file. Unless the user clicks the button in under 2 seconds (because of your This is something I’d like to address in an update to Comlink where |
I'd love that! I'm just beginning to use comlink-wrapped Workers in a Vue component and was wondering why the very simple [edit: this only happens when I initialize more than one single Worker in a single component If the |
I wrote myself thin wrappers with an EventListener that waits for a simple The environment consists of // @/worker/spawn.ts
import { wrap as comlink, type Remote } from "comlink";
/** An object with both the original Worker and the Comlink-wrapped Remote of it. */
export type RemoteWorker<T> = { worker: Worker, remote: Remote<T> };
/** The message expected by the `readinessListener`. */
export const Ready = { ready: true };
/** Listen for the readiness message from the Worker and call the `callback` once. */
export function readinessListener(worker: Worker, callback: () => void) {
worker.addEventListener("message", function ready(event: MessageEvent<typeof Ready>) {
if (!!event.data && event.data.ready === true) {
worker.removeEventListener("message", ready);
callback();
};
});
};
/** Create a new Comlink-wrapped Worker of type `T` and attach an optional
* readiness-callback to signal when it's ready to receive requests. **/
export function spawnSync<T>(path: URL | string, ready?: () => void): RemoteWorker<T> {
// create a new worker using the Vite API
// https://vitejs.dev/guide/features.html#web-workers
let worker = new Worker(new URL(path, /* relative to _this_ file */ import.meta.url), { type: "module" });
// optionally attach the callback using readiness listener
if (ready != undefined) readinessListener(worker, ready);
// wrap worker with comlink
let remote = comlink<T>(worker);
return { worker, remote };
};
/** Create a new Comlink-wrapped Worker of type `T` asynchronously. The Promise
* resolves only once the Remote is ready to receive messages. **/
export async function spawn<T>(path: URL | string): Promise<RemoteWorker<T>> {
// create a new worker using the Vite API
// https://vitejs.dev/guide/features.html#web-workers
let worker = new Worker(new URL(path, /* relative to _this_ file */ import.meta.url), { type: "module" });
// create a promise to wait for the readiness message
await new Promise<void>(resolve => readinessListener(worker, resolve));
// now we're ready to wrap the worker with comlink
let remote = comlink<T>(worker);
return { worker, remote };
}; Then expose the Worker like this: // @/worker/simpleworker.ts
import { expose } from "comlink";
import { spawn, Ready } from "./spawn";
export class SimpleWorker {
// ...
}
expose(new SimpleWorker());
postMessage(Ready); // signal the readinessListener And instantiate it like this: import { spawn, spawnSync } from "@/worker/spawn";
import { SimpleWorker } from "@/worker/simpleworker";
// asynchronously with promise
const simple = await spawn<SimpleWorker>("simpleworker");
console.log("SimpleWorker is ready!");
// synchronously with callback
const simple = spawnSync<SimpleWorker>("simpleworker", () => console.log("SimpleWorker is ready!")); |
Just had the same issue with vue and creating and setting up a worker in the setup section of my component. Here is the above solution in Javascript. async function wrap(worker) {
await new Promise((resolve, reject) => {
const controller = new AbortController();
worker.addEventListener('message', (message) => {
if (message?.data?.ready) {
controller.abort();
resolve();
}
}, { signal: controller.signal })
})
return Comlink.wrap(worker);
} In your worker: Comlink.expose(object);
postMessage({ready: true}) And to use it: import Worker from './worker?worker'
await wrap(new Worker()); |
Any repro Vue example? |
Whatever your thoughts are on top-level await, I'm initializing a library using
await
. However, the web worker seems to hang once it encounters theawait
, preventing the rest of the web worker from executing. Any imported file with a top-level await causes this.I've made a reproducible example (make sure to use a Chromium browser). If the
count is {count}
button is clicked, thegetNumberPlusOne
function infakeTopLevelAwait
won't work. If the await is commented out,getNumberPlusOne
executes successfully.I'd like to know if this a limitation in Web Workers or comlink. I'd really like to get this working. And kudos on such a fantastic lib!
The text was updated successfully, but these errors were encountered: