Philipp
Philipp

Reputation: 2516

Is there a way to have different worker functions in one file

Reading about web workers I stumbled across this example from mdn. The worker.js is just once simple function. So when we post a message to the worker the onmessage part is then started by the worker. In the simple example the worker just multiplies two numbers. But what if I want a worker that can also add, divide etc? Do I need to create a new worker file (e.g. worker_add.js) for each function I want my worker to run? Or what is the cleaner way to deal with this?

I was thinking of having a string posted as first argument to the worker

myWorker.postMessage(["method", arg1, arg2]);

and then in my worker I have if/else conditionals which check if the string matches and then execute different code.

importScripts('emscripten.js')

onmessage = function(e) {
    console.log('Message received from main script.');
    if (e.data[0] == "method1")
    {
        Module.method1(e.data[1].byteOffset, e.data[2]);
        postMessage(e.data[1]);        
    }

    else if (e.data[0] == "method2")
    {
        var ret= Module.method2();
        postMessage(ret);
    }

    console.log('Posting message back to main script');
}

Upvotes: 2

Views: 479

Answers (3)

Philipp
Philipp

Reputation: 2516

In the spirit of Emil's answer I found an example on html5rocks. I feel like this is slightly cleaner than Emil's answer.

The main script looks like this

<button onclick="sayHI()">Say HI</button>
<button onclick="unknownCmd()">Send unknown command</button>
<button onclick="stop()">Stop worker</button>
<output id="result"></output>

<script>
  function sayHI() {
    worker.postMessage({'cmd': 'start', 'msg': 'Hi'});
  }

  function stop() {
    // worker.terminate() from this script would also stop the worker.
    worker.postMessage({'cmd': 'stop', 'msg': 'Bye'});
  }

  function unknownCmd() {
    worker.postMessage({'cmd': 'foobard', 'msg': '???'});
  }

  var worker = new Worker('doWork2.js');

  worker.addEventListener('message', function(e) {
    document.getElementById('result').textContent = e.data;
  }, false);
</script>

and the worker doWork2.js like this:

self.addEventListener('message', function(e) {
  var data = e.data;
  switch (data.cmd) {
    case 'start':
      self.postMessage('WORKER STARTED: ' + data.msg);
      break;
    case 'stop':
      self.postMessage('WORKER STOPPED: ' + data.msg +
                       '. (buttons will no longer work)');
      self.close(); // Terminates the worker.
      break;
    default:
      self.postMessage('Unknown command: ' + data.msg);
  };
}, false);

Upvotes: 1

Emil S. J&#248;rgensen
Emil S. J&#248;rgensen

Reputation: 6366

Since you can pass stringified AJAX, you can easily implement conditional logic in your worker.

Simply pass an argument to tell the worker what to do:

//This snippet won't run, it's just to illustrate

//main.js
var worker = new Worker('/js/worker.js');

worker.addEventListener("message", function(event) {
  if (event.data["do"] == "main") {
    console.log("One task complete");
  } else if (event.data["do"] == "second") {
    console.log("Other task complete");
  }
}, false);

function doMainTask(taskParam) {
  worker
    .postMessage({
      taskParam: JSON.stringify(taskParam),
      "do": "main"
    });
}

function doSecondTask(taskParam) {
  worker
    .postMessage({
      taskParam: JSON.stringify(taskParam),
      "do": "second"
    });
}

//worker.js

function messageHandler(event) {
  var obj = JSON.parse(event.data);

  var str = "";
  if (obj["do"] == "main") {
    str = main(obj.taskParam);
  } else if (obj["do"] == "second") {
    str = second(obj.taskParam);
  }
  this.postMessage({
    msg: str,
    "do": obj["do"],
    obj: obj
  });
}
this.addEventListener('message', messageHandler, false);

function main(taskParam) {
  return "main";
}
function second(taskParam) {
  return "second";
}

Upvotes: 2

smnbbrv
smnbbrv

Reputation: 24541

I don't think there is a better way of doing the things you want but passing some metadata within the message itself.

However your implementation could be easily improved. Instead of switcher, just always treat the first argument as a function name, all others are arguments to this function and the function result is directly posted back:

onmessage = function(e) {
  postMessage(Module[e.data[0]].apply(Module, e.data.slice(1)));
}

Simple one line which cares about any amount of the functions in your worker.

If worker function result is asynchronous you can check that the function result is e.g. Promise and post the message on promise resolve instead of just posting the message directly.

Upvotes: 2

Related Questions