GoogleChromeLabs / comlink

Comlink makes WebWorkers enjoyable.
Apache License 2.0
11.26k stars 386 forks source link

Cannot pass in an object with a proxy callback #515

Open d-haxton opened 3 years ago

d-haxton commented 3 years ago

I have an object where I want to have each of the functions be proxied back to the parent, but when passing in that object it tries to serialize the proxied functions rather than just passing them along.

Mini repro:

worker.js

importScripts("https://unpkg.com/comlink@alpha/dist/umd/comlink.js");
// importScripts("../../../dist/umd/comlink.js");

async function remoteFunction(obj) {
  await obj.cb("A string from a worker");
}

Comlink.expose(remoteFunction);

index.html

<!DOCTYPE html>

<script type="module">
  import * as Comlink from "https://unpkg.com/comlink@alpha/dist/esm/comlink.mjs";
  // import * as Comlink from "../../../dist/esm/comlink.mjs";

  function callback(value) {
    alert(`Result: ${value}`);
  }

  async function init() {
    const remoteFunction = Comlink.wrap(new Worker("worker.js"));
    const obj = { cb: Comlink.proxy(callback) }
    await remoteFunction(obj);
  }

  init();
</script>

The only work around I've found right now is passing all the functions in as a ...arg and then remapping them to their original functions back on the webworker.

surma commented 3 years ago

Can you proxy the entire object instead?

-    const obj = { cb: Comlink.proxy(callback) }
-    await remoteFunction(obj);
+    const obj = { cb: callback }
+    await remoteFunction(Comlink.proxy(obj));

The reason your variant does not work is a tradeoff. It used to work, but it requires Comlink to deeply traverse every object that is used as a function parameter or return value. It can make things unexpectedly very slow.

d-haxton commented 3 years ago

I can sort of get it to work.

It looks like Comlink.proxy also only traverses the first layer of the object, so if I had a nested object like:

const obj = { 
   scope: { 
     item1: { 
       cb: () => {}
     }
   }
}

I have to wrap all objects that contain a function in a Comlink.proxy. And then on the worker side things get a bit dirty with all the awaits that are required.

Is it possible to opt-into a deep traverse for proxy? I may just think about trying to flatten the object on the app side, proxy that object, and unflatten on the worker side, seems pretty jank, but may be the cleanest as far as writing worker code.

surma commented 3 years ago

Hm... an opt-in is not the worst idea. I might prototype something here.

tachibana-shin commented 3 years ago

Can you proxy the entire object instead?

-    const obj = { cb: Comlink.proxy(callback) }
-    await remoteFunction(obj);
+    const obj = { cb: callback }
+    await remoteFunction(Comlink.proxy(obj));

The reason your variant does not work is a tradeoff. It used to work, but it requires Comlink to deeply traverse every object that is used as a function parameter or return value. It can make things unexpectedly very slow.

this works for me. but when i try to add another prototype to ` ``js await remoteFunction(Comlink.proxy({ type: "click", cb: () => console.log("cb") }))


the obj.type becomes undefined. at worker i can only access param.cb but param.type doesn't exist

Is there any update?