Coder Social home page Coder Social logo

Comments (6)

surma avatar surma commented on June 3, 2024 5

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 getNumberPlusOne() before the worker is ready. So the message that tells the worker that the function was called just gets lost.

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 setTimeout) it works! See here.

This is something I’d like to address in an update to Comlink where wrap() returns a Promise that you have to await where Comlink waits until the worker calls expose().

from comlink.

ansemjo avatar ansemjo commented on June 3, 2024 2

I wrote myself thin wrappers with an EventListener that waits for a simple { ready: true } message from the Worker before resolving a Promise with the Comlink-wrapped Remote. This probably isn't ripe for a pull request but might serve as inspiration to those that encounter this issue right now.

The environment consists of Vue 3.3.4 with TypeScript 5.0.4 and Vite 4.3.9.

// @/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!"));

from comlink.

mccare avatar mccare commented on June 3, 2024 1

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());

from comlink.

AhmetKaanGuney avatar AhmetKaanGuney commented on June 3, 2024

I had the exact same issue. I think that's because of how import is implemented for the worker.

from comlink.

ansemjo avatar ansemjo commented on June 3, 2024

This is something I’d like to address in an update to Comlink where wrap() returns a Promise that you have to await where Comlink waits until the worker calls expose().

I'd love that! I'm just beginning to use comlink-wrapped Workers in a Vue component and was wondering why the very simple ready() { return true; } function simply seemed to hang. Because the top-level <script setup> block in single-file components does not allow asynchronous functions, I've put the calls in the onMounted lifecycle hook. But the Promises only started resolving after I inserted a delay of several hundred milliseconds.

[edit: this only happens when I initialize more than one single Worker in a single component <script setup> though. If there's only one it seems to be quick enough.]

If the Comlink.wrap() function returns a Promise, I won't be able to use it in the <script setup> block but maybe I'm better off putting it all outside a component anyway. At least then I'll have a sure way of knowing the link is ready. 😊

from comlink.

vinayakkulkarni avatar vinayakkulkarni commented on June 3, 2024

Any repro Vue example?

from comlink.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.