Skip to content

How to pass a function #37

Description

@bestwestern

Usage
Pass either a function or a string containing code.

Could you please show in the example how to pass a function?

Activity

  1. ChristianMurphy commented on Apr 4, 2019

    @ChristianMurphy
  2. chase-moskal commented on Apr 29, 2019

    @chase-moskal

    huh? i don't get it either

    @ChristianMurphy, the example you linked to shows: const api = Comlink.proxy(new Worker("worker.js"));

    however "worker.js" is a string — not a function

    ..does comlink have anything to do with workerize?

  3. bestwestern commented on May 27, 2019

    @bestwestern
    Author

    @ChristianMurphy thank you.
    @chase-moskal No - I think Christian proposed I used another library.

    @developit is brilliant but he should get an intern to do documentation for mere mortals ;)

  4. kairos666 commented on Oct 5, 2019

    @kairos666

    I've had the same problem and couldn't figure out how to directly use a function as parameter. Found a workaround though that may help you keep your code DRY.

    function foo() { return 3; }
    function bar(multiply) { return foo() * multiply; }
    
    const inlineWorkerFile = `
    ${ foo.toString() }
    export ${ bar.toString() }
    `
    
    let worker = workerize(inlineWorkerFile);
    (async () => {
    	console.log('3 * 3 = ', await worker.bar(3));
    	console.log('3 * 1 = ', await worker.bar(1));
    })();
  5. dandv commented on Oct 19, 2019

    @dandv

    @kairos666: the code seems to be doing pretty much what you're suggesting but when trying to actually pass a function, I see that the arguments are not passed to the created worker.

    @surma, since you write a compelling advocacy post on using Workers, might you have a look at this?

  6. developit commented on Dec 28, 2019

    @developit
    Owner

    This turned into documentation, my apologies.

    Passing a function instead of a string is a little more involved than running that function in a Worker. Workerize functions by parsing the code for the string (or function) you pass to find ES Modules exports (like export function foo(){}). It actually turns that code into something that looks like CommonJS before running it in the Worker, which is important for reasons I'll get to in a minute.

    Broken: "Function exports" (not a thing)

    Unfortunately, there's no way to use exports within a Function - the export syntax only works at the root of a module and can't be inside of blocks or functions. So, while Workerize supports passing a function, it's not possible to export things from a function, and without being exported Workerize can't find your nested functions and make them callable from the main thread:

    const { a, b } = workerize(function() {
      export function a() { return 1 }
      export function b() { return 2 }
      // ^^ these are Syntax Errors :(
    })

    Solution: use CommonJS

    So what can we do? As I mentioned above, Workerize transforms ES Modules exports into roughly CommonJS before running code in the Worker context. When passing a function, the first argument to that function is an exports object, which you can decorate with named functions you want to make callable from the main thread!

    There's one hiccup though - because we're skipping the ES Modules parsing step, Workerize needs to be told what function names we have available rather than inferring them from our code. For this, there's a method on all Workerize instances called expose(), which takes a function name and defines it as method on the worker.

    const worker = workerize(function(exports) {
      exports.a = () => 1;
      exports.b = () => 2;
    });
    worker.expose('a');
    worker.expose('b');
    worker.a().then(console.log)  // 1

    Automate exports using a comment annotation

    Obviously this is a little bit more manual than we'd like. However, there's a solution! Workerize's parsing of exports is extremely naive and can be easily tricked. It doesn't even know about comments. That means we can annotate our methods using a comment and have Workerize detect those exports!

    const worker = workerize(function(exports) {
      /*
      export function a() {}
      export function b() {}
      */
      exports.a = () => 1;
      exports.b = () => 2;
    });
    worker.a().then(console.log)  // 1

    I've created a demo of this code on JSFiddle.

    Automate exports by parsing with a Regex

    One risk with the comment decoration approach is that most production minifier setups remove comments. That's definitely not good for us, since doing so would remove the "mirrored" versions of our exported functions on the main thread. Here's a (very) naive wrapper around Workerize that finds commonjs-style exports and exposes them on the Workerize instance:

    function workerizeCjs(func) {
      const w = workerize(func);
      String(func).match(/\bexports\.[\w$]+/g).map(f=>w.expose(f.substr(8)));
      return w;
    }

    (demo)

    Alternative: Use Comlink

    None of these are amazing solutions TBH. For runtime usage, Comlink has far more to offer here and isn't that much larger (it's around 1.1kb). Here's a version of Workerize that is built with Comlink:

    // Assuming Comlink is in scope on the main thread...
    function comlinker(func) {
      return Comlink.wrap(new Worker(URL.createObjectURL(new Blob([
        'importScripts("https://unpkg.com/comlink@4.2.0/dist/umd/comlink.min.js");var exports={},module={exports:exports};('+func+')(exports);Comlink.expose(module.exports);'
      ]))));
    }
    
    // same as workerize, but with all of Comlink's nice nesting+callback+transferables support:
    const worker = comlinker(exports => {
      exports.foo = async () => 'bar';
    });
    
    worker.foo().then(console.log);  // 'bar'

    Alternative 2: Workerize implemented using Comlink

    It's a little awkward to have to load the Comlink dependency separately on the worker and main thread, so I created a demo showing a pared-down inline version of Comlink that shares itself with the Worker thread. That sharing bit is Workerize's main selling point, so you could consider this to be a version of Workerize implemented using Comlink:

    https://jsfiddle.net/developit/kr5j1d6v/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions