📜 ⬆️ ⬇️

We teach webworkers good manners

Sooner or later everyone who has worked with webworkers has a situation when the code turns into a mess like this:

main.js
const worker = new Worker('test.worker.js'); worker.onmessage = (data) => { if(data.eventName === 'someFuncResult') someFunc(); else if(data.eventName === 'someFunc2Result') someFunc2(); }; worker.postMessage({eventName: 'someFunc'}); 

test.worker.js
 self.addEventListener('message', (data) => { if(data.eventName === 'someFunc') { doSomeFunc(); self.postMessage('someFuncResult'); } if(data.eventName === 'someFunc2') { doSomeFunc(); self.postMessage('someFunc2Result'); } }) 


Even if you close your eyes to unreadable code, you may find that you cannot run a single function at the same time several times, so that the calls do not conflict.
After much agony and a terrible code, it was decided to implement a convenient wrapper over the workers.

Goals:
')
- Readability code
- Competitive inquiries
- Asynchronous functions
- Transparent error handling
- Ability to send intermediate results of the procedure

Installation


For installation, you can use npm

 npm install webworker-promise 

Then you can import from it

 const WebWorker = require('webworker-promise'); //main-process const WebWorkerRegister = require('webworker-promise/lib/register'); 

Or download the umd version

In this case, in the absence of commonjs and requirejs, global objects will be available.

- WebWorkerPromise (webworker-promise in cjs, requirejs)
- WebWorkerPromiseRegister - the function will be available in the worker when importing via importScripts ('dist / register.js');

Main functionality


All work with the library is done using promises, so you can use async / await when performing functions

To initialize the “server” in the worker-file, you need to load the webworker-promise / lib / register function and execute it, after which all messages sent to the worker will be processed by the webworker server.

 //ping.worker.js const register = require('webworker-promise/lib/register'); register() .operation('ping', (message) => { //message - hello, return 'pong'; }); 

And on the client:

 //main.js const WebWorker = require('webworker-promise'); const worker = new WebWorker(new Worker('ping.worker.js')); worker.exec('ping', 'hello') .then(result => {/* result - pong*/}); 

Competitive inquiries


Each procedure call is assigned a unique ID to which its result is attached, which allows you to call the same function several times at one time and get the corresponding result regardless of the order of execution.

 register() .operation('hello', ({name, delay}) => { return new Promise((res) => { setTimeout(() => res(`Hello ${name}!`), delay); }); }); 

 worker.exec('hello', {name: 'Bob', delay: 300}) .then(message => {/* message - Hello Bob!*/}); worker.exec('hello', {name: 'Alice', delay: 200}) .then(message => {/* message - Hello Alice!*/}); 

Asynchronous procedures


Asynchronous procedures are defined as well as usual ones with one difference — the handler must return a promise object as a result.

 register() .operation('get-random-text', (limit) => { // fetch     return fetch(`https://baconipsum.com/api/?callback=?type=meat-and-filler&paras=${limit}`) .then(result => result.json()) }); 

 const worker = new WebWorker(new Worker('async.worker.js')); worker.exec('get-random-text', 2) .then(texts => {/*texts -     */}); 

Error processing


If an error occurs during the execution, it will be thrown into promise and can be caught as it is in catch. Since The workers do not know how to exchange the original error object when the error is caught, the stack and the message are pulled out, and the client throws the error object throw {stack: 'error trace', message: 'error message'} . In future versions, error handling will be improved.

 worker.exec('get-random-text', 2) .catch(e => {/* e.message, e.stack */}); 

Sending Intermediate Results


Often there is a situation that you need to send intermediate results of the procedure. For example, if you send a file using a worker and you need to periodically send the download progress. This is what events were introduced for. As mentioned above, each procedure call is assigned its own unique identifier, and so, events are also associated with this identifier.

 register() .operation('get-events', (str, emit) => { return new Promise((res) => { //   20, 90       setTimeout(() => emit('progress', 20), 100); setTimeout(() => emit('progress', 90), 200); setTimeout(() => res('hello'), 300); }); }); 

 worker.exec('get-events', '', [], (eventName, progress) => { if(eventName === 'progress') console.log(progress); // progress, 20, 90 }) .then(result => {/*result - hello*/}); 

Trasferable objects


To exchange data between the main process and the worker without copying the memory, transferable objects were entered into js. In short, when you transfer an arrayBuffer object, instead of copying data from the main thread's memory to the worker's thread, you can simply delegate them, you can read more here .

To pass transferable objects, you need to specify them as the third argument in the form of an array, but to return transferable, you need to return an object of a special class TransferableResponse as a result of the function.

 register() .operation('send-buffer', (obj) => { //     obj.myBuffer; return new register.TransferableResponse({myBuffer}, [myBuffer]); }); 

 worker.exec('send-buffer', {myBuff: myBuffer}, [myBuffer]) .then((obj) => {/* obj.myBuffer */}); 

"Single procedural" workers


If your worker performs only one function, there is no need to declare a separate procedure; you can simply transfer the register handler to the register.

 register( (name) => `Hello ${name}!`); 

 worker.postMessage('hello'); 

All code is open and covered with tests.

Sources on github

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


All Articles