Optimization of function calls from workers (web-workers)

I welcome the distinguished Khabrosocommunity, and as a contribution to this bank of collective intelligence
, I want to share my experience in working with workers.

Workers (Web-workers), is a technology that allows you to run isolated sections of code in a separate thread. The code from the worker does not slow down the GUI, and runs faster than the code on the page, which makes using workers very attractive for demanding calculations such as drawing graphics or cryptography.

Who has not met this technology yet - here you can get acquainted with its basics.

Below, a small life hack is described, which allows you to reduce the amount of code needed to call functions from a worker if you need to call more than one function.

Usually, if there is only one function in our worker, then we simply write:

outside (in the page code):

var worker = new Worker("myscript.js");  
worker.onmessage (event.data){workerСallback(event.data);}
function workerСallback(data){ /*do something with data object;*/}

inside (in the worker code):

onmessage = function (event) {postMessage(mySingleFunction(event.data));}

So far, everything is simple and elegant.

However, if you add another function to the worker that is called from outside, the amount of code that provides the call to these functions will increase, and it will not look so elegant anymore:


function firstFunctionСallback(data){   /*do something with data object;*/}
function secondFunctionСallback(data){ }
worker.onmessage (msg){
  if(msg.data.callback == "firstFunctionСallback"){
  if(msg.data.callback == "secondFunctionСallback"){
worker.postMessage({functionName: "firstFunction", data: data);


onmessage = function (event) {
  var functionName = event.data.functionName;
  if(functionName == "firstFinction"){
      postMessage({callback: "firstFunctionСallback", result: firstFinction(event.data.data)});
  if(functionName == "secondFunction"){
      postMessage({callback: "secondFunctionСallback", result: secondFunction(event.data.data)});

With this approach, it is not possible to use anonymous functions as callbacks, and each time you add a new function to the worker, you need to write a certain amount of additional code to call this function each time, outside and inside the worker.

To avoid this, the worker can be wrapped in an object that will perform this work.

We call such an object, respectively, Performer, and place it in the external code:

function Performer(scriptSource) {
	var worker = new Worker(scriptSource), callbacks = {}, nextRequestId = 0;	
	this.perform = function(functionName, params, callback) {
		callbacks["request_" + (++nextRequestId)] = callback;
		 {functionName: functionName, params: params, requestId: nextRequestId}
	worker.onmessage = function(msg) {
		callbacks["request_" + msg.data.requestId](msg.data.result);
		delete callbacks["request_" + msg.data.requestId];

In the inner code of the worker, change the external message handler:

onmessage = function (event) {      
  var requestId = event.data.requestId;
  var workerFunction = eval(event.data.functionName);
  var params = event.data.params;
  var result =  workerFunction(params);
  postMessage({result: result, requestId: requestId}); 

Now, you can add any functions to the worker, and call them from the outside, without writing auxiliary code, and also use anonymous functions in callbacks:

var performer = new Performer("myscript.js");
performer.perform("firstFunction", {some: "some"}, function(result){console.log("result1="+result);});	
performer.perform("secondFunction", {some: "some"}, function(result){console.log("result2="+result);});	 

If the worker is not located in a separate script file, but is embedded in the page,
then cross-browser differences should be taken into account in the performer code.

With their consideration, the part of the performer that is responsible for initializing the worker will look like this:

function Performer(scriptText) {
    var worker = null;    
	try {// Firefox
		var Url = window.webkitURL || window.URL;
		worker = new Worker(Url.createObjectURL(new Blob([ scriptText ])));
	} catch (browserNotSupportWindowUrl) {
		try {// Chrome
			worker = new Worker('data:application/javascript,' + encodeURIComponent(scriptText));
		} catch (browserNotSupportWorkers) {// Opera					
			worker = {
				postMessage : function(data) {
					var workerFunction = eval(data.functionName);	
						data : {
							result : workerFunction(data.params),
							requestId : data.requestId

and the creation, respectively, as follows:

var performer = new Performer($('#myscript').text());

Thus, even in browsers that do not support workers, the worker code will still execute, just slower.

UPD: thank the Unknown Benefactor for an invite).

Also popular now: