Event Listeners and Web Workers

I recently figured out the Web Workers API . It is unfortunate that I did not take the time to this well-supported tool before. Modern web applications are very demanding on the capabilities of the main JavaScript execution thread. This affects the productivity of projects and their ability to ensure convenient user experience. Web workers are exactly what these days can help a developer to create fast and convenient web projects.



The moment I understood everything


Web workers have many positive qualities. But I really realized their usefulness when I came across a situation where a certain application uses several DOM event listeners. Such as events of form submission, window resizing, clicks on buttons. All of these listeners must work in the main thread. If the main thread is overloaded with certain operations that take a long time to complete, this has a bad effect on the reaction rate of event listeners to user influences. The application "slows down", events are waiting for the release of the main thread.

I must admit that the reason why it was the event listeners that interested me so much was because I initially misunderstood what tasks web-workers are designed to solve. At first, I thought they could help in improving the speed of code execution. I thought that an application would be able to do much more in a certain period of time in the event that some fragments of its code would be executed in parallel, in separate threads. But during the execution of web project code, a situation is quite common when, before you start to do something, you need to wait for some event. Let's say the DOM needs to be updated only after some calculations are completed. Knowing this, I naively believed that if I, in any case, have to wait, it means that it makes no sense to transfer the execution of some code to a separate thread.

Here is an example of code that you can recall here:

const calculateResultsButton = document.getElementById('calculateResultsButton'); const openMenuButton = document.getElementById('#openMenuButton'); const resultBox = document.getElementById('resultBox'); calculateResultsButton.addEventListener('click', (e) => {     // "    -, ,   ,     //   DOM   ?"     const result = performLongRunningCalculation();     resultBox.innerText = result; }); openMenuButton.addEventListener('click', (e) => {     //      . }); 

Here I update the text in the field after some calculations are completed, presumably lengthy. It seems to be pointless to run this code in a separate thread, since the DOM does not update before this code completes. As a result, of course, I decide that this code needs to be executed synchronously. However, seeing such a code, at first I did not understand that as long as the main thread is blocked, other event listeners do not start. This means that "brakes" begin to appear on the page.

How to "slow down" the page


Here is a CodePen project demonstrating the above.


A project demonstrating a situation in which pages are slow

Pressing the Freeze button causes the application to start solving the synchronous task. All this takes 3 seconds (it simulates the performance of lengthy calculations). If at the same time click on the Increment button, then until 3 seconds have passed, the value in the Click Count field will not be updated. A new value corresponding to the number of clicks on Increment will be written to this field only after three seconds have passed. The main stream is blocked during a pause. As a result, everything in the application window looks inoperative. The application interface is frozen. Events arising in the process of “freezing” are waiting for the opportunity to use the resources of the main stream.

If you click on Freeze and try to resize me! , then, again, until three seconds have passed, the size of the field will not change. And after that, the size of the field, however, will change, but there is no need to talk about any “smoothness” in the interface.

Event listeners are a much larger phenomenon than it might seem at first glance


Any user will not like to work with a site that behaves as shown in the previous example. But here only a few event listeners are used here. In the real world, we are talking about completely different scales. I decided to use getEventListeners method in Chrome and, using the following script, find out the number of event listeners attached to the DOM elements of various pages. This script can be run directly in the developer tool console. There he is:

 Array  .from([document, ...document.querySelectorAll('*')])  .reduce((accumulator, node) => {    let listeners = getEventListeners(node);    for (let property in listeners) {      accumulator = accumulator + listeners[property].length    }    return accumulator;  }, 0); 

I ran this script on different pages and found out about the number of event listeners used on them. The results of my experiment are shown in the following table.
application
Number of Event Listeners
Dropbox
602
Google messages
581
Reddit
692
YouTube
6054 (!!!)

You can not pay attention to specific numbers. The main thing here is that we are talking about a very large number of event listeners. As a result, if at least one long-running operation in the application goes wrong, all these listeners will stop responding to user influences. This gives developers many ways to upset the users of their applications.

Get rid of the “brakes” with web workers


Given all of the above, let's rewrite the previous example. Here is his new version. It looks exactly like the old one, but inside it is arranged differently. Namely, now the operation that used to block the main thread has been moved to its own thread. If you do the same with this example as with the previous one, you can notice serious positive differences. Namely, if after clicking on the Freeze button click on Increment , the Click Count field will be updated (after the web worker finishes, in any case, the number 1 will be added to the Click Count value). The same goes for resize me! . Code running in a separate thread does not block event listeners. This allows all elements of the page to remain operational even during the execution of an operation that previously simply “froze” the page.

Here is the JS code for this example:

 const button1 = document.getElementById('button1'); const button2 = document.getElementById('button2'); const count = document.getElementById('count'); const workerScript = `  function pause(ms) {    let time = new Date();    while ((new Date()) - time <= ms) {}               }  self.onmessage = function(e) {    pause(e.data);    self.postMessage('Process complete!');  } `; const blob = new Blob([  workerScript, ], {type: "text/javascipt"}); const worker = new Worker(window.URL.createObjectURL(blob)); const bumpCount = () => {  count.innerText = Number(count.innerText) + 1; } worker.onmessage = function(e) {  console.log(e.data);  bumpCount(); } button1.addEventListener('click', async function () {  worker.postMessage(3000); }); button2.addEventListener('click', function () {  bumpCount(); }); 

If you dig deeper into this code, you will notice that, although the Web Workers API could be arranged and more comfortable, there is nothing particularly scary in working with it. This code probably looks scary due to the fact that this is a simple, quickly written demo. In order to improve the usability of the API and make it easier to work with web workers, you can use some additional tools. For example, the following seemed interesting to me:

  • Workerize - allows you to run modules in web workers.
  • Greenlet - makes it possible to execute arbitrary pieces of asynchronous code in web workers.
  • Comlink - provides a convenient layer of abstraction over the Web Workers API.

Summary


If your web application is a typical modern project, it means that it is very likely that it has many event listeners. It is also possible that it, in the main thread, performs many calculations, which can quite be performed in other threads. As a result, you can provide a good service to both your users and listeners of events, entrusting “heavy” calculations to web-workers.

It is worth noting that excessive enthusiasm for web workers and the removal of everything that is not directly related to the user interface to web workers is probably not the best idea. Such processing of the application may require a lot of time and effort, the project code will become more complicated, and the benefits of such a conversion will be very small. Instead, it might be worth starting by looking for a truly “heavy” code and taking it out to web workers. Over time, the idea of ​​using web-workers will become more familiar, and you will probably be guided by it even at the stage of interface design.

Be that as it may, I recommend that you understand the Web Workers API. This technology enjoys very broad browser support, and the performance requirements of modern web applications are growing. Therefore, we have no reason to refuse to study tools like web workers.

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


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


All Articles