Écouteurs d'événements et travailleurs du Web

J'ai récemment découvert l'API Web Workers . Il est regrettable que je n'ai pas pris le temps de cet outil bien pris en charge auparavant. Les applications Web modernes sont très exigeantes sur les capacités du thread d'exécution JavaScript principal. Cela affecte la productivité des projets et leur capacité à garantir une expérience utilisateur pratique. Les travailleurs Web sont exactement ce que ces jours-ci peuvent aider un développeur à créer des projets Web rapides et pratiques.



Au moment où j'ai tout compris


Les travailleurs du Web ont de nombreuses qualités positives. Mais j'ai vraiment réalisé leur utilité lorsque je suis tombé sur une situation où une certaine application utilise plusieurs écouteurs d'événements DOM. Tels que les événements de soumission de formulaire, le redimensionnement des fenêtres, les clics sur les boutons. Tous ces écouteurs doivent travailler dans le thread principal. Si le thread principal est surchargé de certaines opérations qui prennent beaucoup de temps, cela a un mauvais effet sur la vitesse de réaction des écouteurs d'événements aux influences de l'utilisateur. L'application "ralentit", les événements attendent la sortie du thread principal.

Je dois admettre que la raison pour laquelle ce sont les auditeurs d'événements qui m'ont autant intéressé, c'est parce que j'ai initialement mal compris les tâches que les travailleurs Web sont censés résoudre. Au début, je pensais qu'ils pourraient aider à améliorer la vitesse d'exécution du code. Je pensais qu'une application serait capable de faire beaucoup plus dans un certain laps de temps au cas où certains fragments de son code seraient exécutés en parallèle, dans des threads séparés. Mais pendant l'exécution du code de projet Web, une situation est assez courante lorsque, avant de commencer à faire quelque chose, vous devez attendre un événement. Disons que le DOM doit être mis à jour uniquement après que certains calculs soient terminés. Sachant cela, je croyais naïvement que si, de toute façon, je devais attendre, cela signifiait que cela n'avait aucun sens de transférer l'exécution d'un code sur un thread séparé.

Voici un exemple de code que vous pouvez rappeler ici:

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) => {     //      . }); 

Ici, je mets à jour le texte dans le champ après que certains calculs soient terminés, probablement longs. Il semble inutile d'exécuter ce code dans un thread séparé, car le DOM ne se met pas à jour avant la fin de ce code. En conséquence, bien sûr, je décide que ce code doit être exécuté de manière synchrone. Cependant, en voyant un tel code, au début, je n'ai pas compris que tant que le thread principal est bloqué, les autres écouteurs d'événements ne démarrent pas. Cela signifie que des "freins" commencent à apparaître sur la page.

Comment "ralentir" la page


Voici un projet CodePen démontrant ce qui précède.


Un projet démontrant une situation dans laquelle les pages sont lentes

En appuyant sur le bouton Freeze l'application commence à résoudre la tâche synchrone. Tout cela prend 3 secondes (il simule les performances de longs calculs). Si en même temps cliquez sur le bouton Increment , puis jusqu'à ce que 3 secondes se soient écoulées, la valeur dans le champ Click Count ne sera pas mise à jour. Une nouvelle valeur correspondant au nombre de clics sur Increment ne sera écrite dans ce champ qu'après trois secondes. Le flux principal est bloqué pendant une pause. Par conséquent, tout dans la fenêtre d'application semble inopérant. L'interface d'application est gelée. Les événements survenant lors du processus de «gel» attendent l’occasion d’utiliser les ressources du flux principal.

Si vous cliquez sur Freeze et essayez de resize me! , puis, encore une fois, jusqu'à ce que trois secondes se soient écoulées, la taille du champ ne changera pas. Et après cela, la taille du champ, cependant, changera, mais il n'est pas nécessaire de parler de «fluidité» dans l'interface.

Les auditeurs d'événements sont un phénomène beaucoup plus important qu'il n'y paraît à première vue


Tout utilisateur n'aimera pas travailler avec un site qui se comporte comme indiqué dans l'exemple précédent. Mais ici, seuls quelques écouteurs d'événements sont utilisés ici. Dans le monde réel, nous parlons d'échelles complètement différentes. J'ai décidé d'utiliser la méthode getEventListeners dans Chrome et, à l'aide du script suivant, de trouver le nombre d'écouteurs d'événements attachés aux éléments DOM de différentes pages. Ce script peut être exécuté directement dans la console de l'outil développeur. Le voici:

 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); 

J'ai exécuté ce script sur différentes pages et découvert le nombre d'écouteurs d'événements utilisés sur eux. Les résultats de mon expérience sont présentés dans le tableau suivant.
App
Nombre d'auditeurs d'événements
Dropbox
602
Messages Google
581
Reddit
692
YouTube
6054 (!!!)

Vous ne pouvez pas prêter attention à des numéros spécifiques. L'essentiel ici est que nous parlons d'un très grand nombre d'auditeurs d'événements. Par conséquent, si au moins une opération de longue durée dans l'application ne fonctionne pas, tous ces écouteurs cesseront de répondre aux influences de l'utilisateur. Cela donne aux développeurs de nombreuses façons de déranger les utilisateurs de leurs applications.

Débarrassez-vous des «freins» avec les travailleurs du Web


Compte tenu de tout ce qui précède, réécrivons l'exemple précédent. Voici sa nouvelle version. Il ressemble exactement à l'ancien, mais à l'intérieur, il est disposé différemment. A savoir, maintenant l'opération qui bloquait le thread principal a été déplacée vers son propre thread. Si vous faites la même chose avec cet exemple qu'avec le précédent, vous pouvez remarquer de sérieuses différences positives. À savoir, si après avoir cliqué sur le bouton Freeze cliquez sur Increment , le champ Click Count sera mis à jour (une fois que le travailleur Web aura terminé, dans tous les cas, le numéro 1 sera ajouté à la valeur Click Count ). Il en va de même pour resize me! . Le code exécuté dans un thread séparé ne bloque pas les écouteurs d'événements. Cela permet à tous les éléments de la page de rester opérationnels même pendant l'exécution d'une opération qui auparavant «gelait» simplement la page.

Voici le code JS pour cet exemple:

 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(); }); 

Si vous approfondissez ce code, vous remarquerez que, bien que l'API Web Workers puisse être organisée et plus confortable, il n'y a rien de particulièrement effrayant à travailler avec. Ce code semble probablement effrayant, car il s'agit d'une démo simple et rapidement écrite. Afin d'améliorer la convivialité de l'API et de faciliter la collaboration avec les travailleurs Web, vous pouvez utiliser des outils supplémentaires. Par exemple, les éléments suivants m'ont paru intéressants:

  • Workerize - vous permet d'exécuter des modules dans les travailleurs Web.
  • Greenlet - permet d'exécuter des morceaux arbitraires de code asynchrone dans les travailleurs Web.
  • Comlink - fournit une couche d'abstraction pratique sur l'API Web Workers.

Résumé


Si votre application Web est un projet moderne typique, cela signifie qu'il est très probable qu'elle dispose de nombreux écouteurs d'événements. Il est également possible qu'il, dans le thread principal, effectue de nombreux calculs, qui peuvent tout à fait être effectués dans d'autres threads. En conséquence, vous pouvez fournir un bon service à la fois à vos utilisateurs et à vos auditeurs d'événements, en confiant des calculs «lourds» aux travailleurs du Web.

Il convient de noter que l'enthousiasme excessif pour les travailleurs Web et la suppression de tout ce qui n'est pas directement lié à l'interface utilisateur pour les travailleurs Web n'est probablement pas la meilleure idée. Un tel traitement de l'application peut nécessiter beaucoup de temps et d'efforts, le code du projet deviendra plus compliqué et les avantages d'une telle conversion seront très faibles. Au lieu de cela, il pourrait être utile de commencer par rechercher un code vraiment «lourd» et de le présenter aux travailleurs du Web. Au fil du temps, l'idée d'utiliser des web-travailleurs deviendra plus familière, et vous serez probablement guidé par elle même au stade de la conception de l'interface.

Quoi qu'il en soit, je vous recommande de comprendre l'API Web Workers. Cette technologie bénéficie d'une prise en charge très large des navigateurs et les exigences de performances des applications Web modernes augmentent. Par conséquent, nous n'avons aucune raison de refuser d'étudier des outils comme les travailleurs du Web.

Chers lecteurs! Utilisez-vous des web travailleurs dans vos projets?


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


All Articles