⬆️ ⬇️

How JS works: web workers and five use cases

[We advise you to read] Other 19 parts of the cycle
Part 1: Overview of the engine, execution time mechanisms, call stack

Part 2: About the V8 internals and code optimization

Part 3: Memory management, four types of memory leaks and dealing with them

Part 4: Event loop, asynchrony, and five ways to improve code with async / await

Part 5: WebSocket and HTTP / 2 + SSE. What to choose?

Part 6: Features and Scope of WebAssembly

Part 7: Web Workers and Five Use Cases

Part 8: Service Workers

Part 9: Web push notifications

Part 10: Tracking DOM Changes with MutationObserver

Part 11: The engines of rendering web pages and tips to optimize their performance

Part 12: Browser networking subsystem, optimizing its performance and security

Part 12: Browser networking subsystem, optimizing its performance and security

Part 13: Animation with CSS and JavaScript

Part 14: How JS works: abstract syntax trees, parsing and its optimization

Part 15: How JS Works: Classes and Inheritance, Babil and TypeScript Transformation

Part 16: How JS Works: Storage Systems

Part 17: How JS Works: Shadow DOM Technology and Web Components

Part 18: How JS: WebRTC and P2P Communication Mechanisms Work

Part 19: How JS Works: Custom Elements


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