📜 ⬆️ ⬇️

How JS works: web workers and five use cases


We publish the translation of the seventh part of a series of materials about the features of the various mechanisms of JavaScript. Our topic today is web workers. In particular, it will focus on the different types of web workers, how the joint work of the parts of which they are composed, as well as their capabilities and limitations that can be encountered in different scenarios of their use, is organized. It will also show 5 options for the practical use of web workers.

image

Asynchronous Programming Limitations


Before we start talking about web workers, it’s worth remembering that JavaScript is a single-threaded language, however, it also supports asynchronous code execution.

One of the previous materials in this series was devoted to asynchronous programming and its use in JS projects.
')
Asynchronous code execution allows the user interface of web applications to function normally and respond to user commands. The system “schedules” the load on the event loop in such a way that the operations associated with the user interface are performed first.

A good example of the use of asynchronous programming methods demonstrates the technique of performing AJAX requests. Since waiting for a response can take a lot of time, requests can be made asynchronously, and while the client is waiting for a response, code that is not related to the request can be executed.

//   jQuery jQuery.ajax({    url: 'https://api.example.com/endpoint',    success: function(response) {        // ,           } }); 

This approach, however, demonstrates the following problem: requests are processed by the browser's WEB API. We are also interested in the possibility of asynchronous execution of arbitrary code. Say, what if the code inside the callback function uses processor resources intensively?

 var result = performCPUIntensiveCalculation(); 

If the performCPUIntensiveCalculation function is not something like an asynchronously executed HTTP request, but code that blocks the main thread (say, a huge and heavy for loop), then with a single-threaded approach to JS development we have no way to release the event loop and unlock the browser interface. As a result, the user will not be able to work with him normally.

This means that asynchronous functions mitigate only a small part of the limitations associated with single-threading JS.

In some cases, a good result in unloading the main stream when performing resource-intensive operations can be achieved with the help of setTimeout . For example, if you break up complex calculations into fragments performed in different setTimeout calls, you can “distribute” them across the event loop, and thus not block the user interface.

Take a look at a simple function that calculates the average for a numeric array.

 function average(numbers) {   var len = numbers.length,       sum = 0,       i;   if (len === 0) {       return 0;   }     for (i = 0; i < len; i++) {       sum += numbers[i];   }    return sum / len; } 

This code can be rewritten so that it “emulates” asynchronous execution:

 function averageAsync(numbers, callback) {   var len = numbers.length,       sum = 0;   if (len === 0) {       return 0;   }   function calculateSumAsync(i) {       if (i < len) {           //       .           setTimeout(function() {               sum += numbers[i];               calculateSumAsync(i + 1);           }, 0);       } else {           //     ,              callback(sum / len);       }   }   calculateSumAsync(0); } 

With this approach, we use the setTimeout function, which schedules the execution of calculations. This leads to the placement in the event loop of a function that performs the next portion of the calculations, so that there is enough time between the sessions for performing this function for other calculations, including those related to the user interface.

Web workers


HTML5 has given us many great features, among which are the following:


Web workers are streams owned by the browser that can be used to execute JS code without locking the event loop.

This is truly a great opportunity. The JavaScript concept system is based on the idea of ​​a single-threaded environment, and now we have a technology that (partially) removes this restriction.

Web workers allow the developer to place tasks that require lengthy and complex calculations that use the processor intensively in background threads without blocking the user interface, which allows applications to respond quickly to user input. Moreover, we no longer need workarounds, like the above setTimeout trick to find an acceptable way to interact with the event loop.

Here is a simple example that demonstrates the difference between sorting an array with and without a web worker.

â–Ť Web Worker Review


Web workers allow you to perform computationally heavy and lengthy tasks without blocking the user interface flow. In fact, when using them, calculations are performed in parallel. Before us is a real multithreading.

You may recall that JavaScript is a single-threaded programming language. Perhaps, here you should realize that JS is a language that does not define a thread model. Web workers are not part of JavaScript. They represent a browser feature that can be accessed via javascript. Most browsers have historically been single-threaded (this situation has, of course, changed), and most JavaScript implementations are designed for browsers.

Web workers are not implemented in Node.js - there is the concept of "clusters" or "child processes", and this is a little different.

It is worth noting that the specification mentions three types of web workers:


â–ŤDedicated Workers


Instances of dedicated web workers are created by the main process. Only he can exchange data with them.


Browser support for dedicated workers

â–ŤShared Workers


Any process that has the same source as the worker can get access to the shared worker (for example, different browser tabs, iframe , and other shared workers).


Support for shared workers in browsers

Service Workers


Service workers are event-driven workers registered using their origin and path. They can control the web page with which they are connected, intercepting and modifying navigation commands and resource requests, and performing data caching that can be very precisely controlled. All this gives us excellent means of controlling the behavior of the application in a certain situation (for example, when the network is not available).


Support for service workers in browsers

It should be noted that in this material we will deal with dedicated workers, we will keep them in mind when talking about “web workers” or “workers”.

How web workers work


Web workers are implemented using .js files, which are included on the page using an asynchronous HTTP request. These requests are completely hidden from the developer thanks to the Web Worker API .

Workers use message transfer mechanisms that are characteristic of technologies that are used to organize the interaction of threads, which allows them to be organized in parallel. They are great for performing heavy computational operations without slowing down the user interface.

Web workers are run in isolated threads in the browser. As a result, the code they execute must be included in a separate file. It is important to remember.

Here's how web workers are created:

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

If the task.js file exists and is accessible to it, the browser will create a new thread that loads this file asynchronously. After the download is complete, the workman’s code will begin to run. If the browser receives an error message 404 when trying to download a file, the file will not be loaded, and no error messages will be displayed.

To run a newly created worker, call his postMessage method:

 worker.postMessage(); 

Data exchange with web worker


In order for the page that created the web worker to interact with it, you need to use either the postMessage method or the broadcast data channel represented by the BroadcastChannel object.

Post postMessage method


When calling this method, newer browsers support, as the first parameter, a JSON object, and in older browsers only a parameter of the String type is supported.

Let's look at an example of how the page that created the worker can exchange data with it using a JSON object. When you send a string, everything looks almost the same.

Here is part of the HTML page:

 <button onclick="startComputation()">Start computation</button> <script> function startComputation() {   worker.postMessage({'cmd': 'average', 'data': [1, 2, 3, 4]}); } var worker = new Worker('doWork.js'); worker.addEventListener('message', function(e) {   console.log(e.data); }, false); </script> 

Here is the contents of the worker code file:

 self.addEventListener('message', function(e) { var data = e.data; switch (data.cmd) {   case 'average':     var result = calculateAverage(data); // ,          self.postMessage(result);     break;   default:     self.postMessage('Unknown command'); } }, false); 

When the button is pressed, the page invokes the postMessage method of the worker. This call passes the JSON object to the worker with the cmd and data keys and their corresponding values. The worker will process this message using the message handler specified in it.

When the worker receives the message and understands what is wanted of him, he will perform the calculations on his own, without blocking the cycle of events. What the worker is doing looks like a standard JS function. When the calculations are completed, their results are transmitted to the main page.

In the context of the worker, both self and this indicate a global namespace for the worker.

In order to stop the worker, you can use one of two methods. The first is to call from the main page the worker.terminate() method. The second is executed inside the worker and is implemented by the self.close() command.

â–ŤBroadcast channel


The BroadcastChannel object is a more universal API for data transmission. It allows you to send messages that can be accepted in all contexts that have the same source. All browser tabs, iframe or workers from the same source can send and receive broadcast messages:

 //     var bc = new BroadcastChannel('test_channel'); //    bc.postMessage('This is a test message.'); //    ,  //     bc.onmessage = function (e) { console.log(e.data); } //    bc.close() 

Here is the scheme of interaction between various entities using a broadcast messaging channel:

Data Exchange Using Broadcast Message Channel

However, it is worth noting that the BroadcastChannel object still has rather limited support in browsers.


BroadcastChannel support in browsers

Ways to send messages to web workers


There are two ways to send messages to web workers. The first is to copy the data, the second is to transfer data from the source to the receiver without copying them. Consider these ways to work with messages:


Features available to web workers


For web workers, due to their multi-threading nature, only a limited set of JavaScript features is available. Here are these features:


Limitations of web workers


Unfortunately, web workers do not have access to some very important features of JavaScript. Among them are the following:


All of this means that web workers cannot manipulate the DOM (and thus cannot directly affect the user interface). At first, it may seem that this makes it much more difficult to use web workers, but over time, having learned how to use web workers correctly, you will begin to perceive them as separate “computers”, while what relates to working with web workers. user interface will be executed in the page code. Workers will perform heavy calculations, and after the work is completed, send the results to the page that calls them, the code of which will already make the necessary changes to the user interface.

Error processing


As with any JS code, web workers need to handle errors. If an error occurs during the execution of the worker, the ErrorEvent event is ErrorEvent . The error object contains three useful properties that allow you to understand its essence:


Here is a sample code for handling errors in a web worker:

 function onError(e) { console.log('Line: ' + e.lineno); console.log('In: ' + e.filename); console.log('Message: ' + e.message); } var worker = new Worker('workerWithError.js'); worker.addEventListener('error', onError, false); worker.postMessage(); //    . 

Here is the worker code

 self.addEventListener('message', function(e) { postMessage(x * 2); //  . 'x'  . }; 

Here you can see how we created the worker and assigned him an error event handler.

Inside the worker (second code snippet) we intentionally raise an exception by multiplying x by 2 while x not defined in the current scope. The exception reaches the source script and an onError handler is onError that onError the error information.

Web Worker Usage Scenarios


We talked about the strengths and weaknesses of web workers. Now consider several scenarios for their use.


Results


In this article, we talked about web workers — a relatively new feature available to web developers in most modern browsers. Web workers allow you to carry out resource-intensive operations into separate threads, which allows you to not load the main stream, which can easily handle everything related to the user interface.

Previous parts of a series of articles:

Part 1: How JS Works: Overview of the Engine, Runtime Mechanisms, Call Stack
Part 2: How JS Works: About V8 Inside and Code Optimization
Part 3: How JS works: memory management, four types of memory leaks and how to deal with them
Part 4: How JS works: event loop, asynchrony, and five ways to improve code with async / await
Part 5: How JS: WebSocket and HTTP / 2 + SSE work. What to choose?
Part 6: How JS Works: Features and Scope of WebAssembly

Dear readers! Do you use web workers in your projects?

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


All Articles