[We advise you to read] Other 19 parts of the cycle
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.
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:
- SSE (we considered and compared this technology with the WebSocket protocol in one of the previous materials ).
- Geolocation.
- Application cache
- Local storage.
- Technology Drag and Drop.
- Web workers.
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:
- Copy message. The message is serialized, copied, sent, and then, on the receiving side, deserialized. The page and the worker do not use a shared copy of the message, so here we are faced with the creation of copies of data in each session of sending messages. Most browsers implement this feature by automatically converting the transmitted information into JSON on the transmitter side and decoding this data on the receiver side. As you might expect, this adds a significant load on the system when sending messages. The larger the message, the longer it will take to send it.
- Send a message. With this approach, it turns out that the sender of the message can no longer use the message after it has been sent. In this case, the transfer of messages is performed almost instantly. The main feature of this method is that with its help you can only transfer an ArrayBuffer object.
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:
navigator
objectlocation
object (read only)
XMLHttpRequest
setTimeout()/clearTimeout()
and setInterval()/clearInterval()
- Application cache
- Import external scripts using
importScripts()
- Creating other web workers
Limitations of web workers
Unfortunately, web workers do not have access to some very important features of JavaScript. Among them are the following:
- DOM (this is not thread safe)
- Window object
document
objectparent
object
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:
filename
is the name of the file containing the worker script that caused the error.lineno
is the line number where the error occurred.
message
- description of the error.
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.
- 3D rendering scenes. In particular, we are talking about the implementation of the ray-tracing method - a rendering technique that allows you to create images by tracking the direction of light rays and determining the color of pixels. Ray tracing uses intensive mathematical calculations to model the propagation of light. With this approach, effects such as reflections and refractions are realized, ray tracing allows you to simulate the appearance of various materials, and so on. All this computational logic can be moved to a web worker so that it does not block the user interface flow. You can make it even more interesting, namely, divide the rendering of the image between several workers (and, accordingly, between several processor cores). Here is a simple example of implementing ray tracing using web workers.
- Encryption. End-to-end encryption is becoming increasingly popular due to increasing attention to regulating the distribution of personal and confidential data. Encryption operations can be quite long, especially if there is a need for frequent encryption of large amounts of data. This is a very appropriate scenario for using a web worker, since you don't need access to DOM objects or something like that. Encryption is information processing algorithms that are sufficiently basic JS features. When encryption is performed by a worker, this does not affect the user's experience with the site interface.
- Preloading data. In order to optimize the website and improve the user experience of working with it, you can use web workers to download and store some data in advance, which you can use very quickly when the need arises later. Web workers are great for this use case, as the operations they perform will not affect the application interface, unlike the preloading of data implemented by the main stream.
- Progressive web applications. Such applications should, even with an unreliable network connection, load quickly. This means that the data must be stored in the browser locally. This is where IndexedDB or a similar API comes into play . In general, we are talking about the need to maintain some kind of data storage on the client side. In order to work with this repository, without blocking the user interface, the work must be organized in a web worker. It should be noted here that, in the case of IndexedDB, there is an asynchronous API that allows you to not load the main thread without web workers, but earlier there was a synchronous API (which may appear again) that you need to use only inside web workers.
- Spell checker A simple spell checker works like this: the program reads a dictionary file with a list of correctly written words. A search tree is formed from the dictionary, which provides an effective search in the text. When a word is sent to the system for verification, it checks its presence in the search tree. If the word cannot be found, the user can be provided with alternative variants of this word, obtained by replacing the symbols of the original word and searching the obtained words in the tree to check whether they are correct from the point of view of the verification system. All this can be easily transferred to the web worker, which will allow the user to work with the text without experiencing the problems associated with locking the interface when checking the word and when searching for alternative spellings.
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?
