📜 ⬆️ ⬇️

Optimization of function calls from workers (web-workers)

Greetings to the respected Habrosocommunity, 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 the use of workers very attractive, for resource-intensive calculations, such as drawing graphics or cryptography.

Who has not met with this technology - 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, we simply write:

outside (in the page code):

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


inside (in 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 externally, then the amount of code that calls these functions will increase, and it will not look so elegant:

outside:

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


inside:

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


With this approach, it is not possible to use anonymous functions as callbacks, and each time a new function is added to the worker, each time, outside and inside the worker, you need to write some additional code that calls this function.

To avoid this - the worker can be wrapped in an object that will do the job.

Let's call such an object, respectively, Performer, and place it in external code:

 function Performer(scriptSource) { var worker = new Worker(scriptSource), callbacks = {}, nextRequestId = 0; this.perform = function(functionName, params, callback) { callbacks["request_" + (++nextRequestId)] = callback; worker.postMessage( {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 internal code of the worker, change the handler of external messages:

 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 any helper 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 in the code of the performer cross-browser differences should be taken into account.

Taking them into account, the part of the performer that is responsible for the initialization of 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 eval(scriptText); worker = { postMessage : function(data) { var workerFunction = eval(data.functionName); worker.onmessage({ 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 code of the worker will still be executed, just slower.

UPD: I thank Unknown Benefactor for an invite).

Source: https://habr.com/ru/post/207890/


All Articles