MySQL Java JavaScript PHP Python HTML-CSS C-sharp C++ Go

Web Worker API

Definirea și executarea unui web worker

JavaScript este un limbaj care rulează în mod monofir, ceea ce înseamnă că mai multe scripturi nu pot rula simultan. Scripturile sunt interpretate și executate unul după altul, linie cu linie.

În plus, declanșarea evenimentelor și gestionarea lor nu se întâmplă în paralel: codul care declanșează un eveniment este suspendat până când sunt executate gestionarele corespunzătoare ale evenimentelor. Același lucru se aplică și callback-urilor, funcțiilor de apel invers. De exemplu, atunci când se trimite o cerere Ajax către server, scriptul care trimite cererea continuă să ruleze până când serverul pregătește răspunsul și îl trimite clientului.

Când callback-ul primește răspunsul serverului, codul înconjurător este suspendat și își reia funcționarea doar când callback-ul finalizează prelucrarea răspunsului serverului.

Web Worker API elimină această limitare, permițând prelucrarea sarcinilor în paralel în fundal. Web workerii rulează în fire de execuție separate. Datorită web workerilor, devine posibil să se execute în fundal, în paralel cu firul principal, diferite scenarii consumatoare de resurse care, altfel, ar avea un impact negativ asupra performanței aplicației web. Firul web workerului poate executa sarcini fără a interfera cu interfața utilizatorului.

Pentru crearea unui web worker se utilizează funcția-constructor Worker:

const worker = new Worker("worker.js");

Sarcinile executate de web workeri sunt definite în fișiere separate, și în funcția-constructor se trece calea către scriptul care va fi executat de web worker.

Web workerul creat cu funcția Worker() mai este numit și web worker dedicat (dedicated web worker).

Trebuie să se țină cont că pentru încărcarea fișierelor web workerilor, pagina web și fișierele web workerilor trebuie să fie amplasate pe un server web. În acest caz, ca server vom utiliza Node.js ca opțiunea cea mai simplă, dar desigur, dacă se dorește, se poate utiliza orice altă tehnologie de server sau un server web.

Să considerăm cel mai simplu exemplu. Vom defini pentru proiect o folder pe discul dur, în care vom crea trei fișiere:

  • index.html: pagina principală a aplicației
  • worker.js: fișierul sarcinii web workerului
  • server.js: fișierul aplicației server Node.js

Definirea paginii web și crearea web workerului

Pe pagina index.html vom defini următorul cod:

<!DOCTYPE html>
<html>
<head>
   <meta charset="utf-8" />
   <title>FDC.COM</title>
</head>
<body>
<script>
const worker = new Worker("worker.js");
</script>
</body>
</html>

Practic aici doar se creează obiectul web workerului, care va executa codul din fișierul "worker.js".

Definirea codului web workerului

În fișierul worker.js vom defini pentru demonstrație cel mai simplu cod:

let result = 1;
const intervalID = setInterval(work, 1000);

function work() {
   result = result * 2;
   console.log("result=", result);
   if(result >= 32) clearInterval(intervalID);
}

Aici cu ajutorul funcției setInterval() la fiecare secundă va fi executată funcția work. În funcția work pur și simplu obținem valoarea variabilei result înmulțită cu 2, salvăm rezultatul înapoi în variabila result și afișăm rezultatul curent pe consolă. Când result atinge limita - numărul 32, oprirea temporizatorului va duce la încheierea scriptului și, respectiv, a sarcinii web workerului.

Definirea serverului

Pentru funcționarea corectă a paginii web, aceasta trebuie lansată de pe un server web. Și în acest caz, în fișierul server.js vom defini codul unui server web local Node.js. Vom defini în el următorul cod:

const http = require("http");
const fs = require("fs");

http.createServer((request, response)=>{
   let filePath = request.url.substring(1);
   if(!filePath) filePath = "index.html";  
   response.setHeader("Content-Type", "text/html; charset=utf-8;");
   fs.readFile(filePath, (error, data)=>{
       if(error){
           response.statusCode = 404;
           response.end("<h1>Resurse not found!</h1>");
       }  
       else{
           response.end(data);
       }
   });
}).listen(3000, ()=>console.log("Serverul a fost lansat la adresa http://localhost:3000"));

Pe scurt, trecem prin cod. La început se conectează pachetele cu funcționalitatea pe care intenționăm să o utilizăm:

const http = require("http");
const fs = require("fs");

Pentru crearea serverului se utilizează funcția http.createServer(). În această funcție se trece o funcție-handler, care este apelată de fiecare dată când la server vine o cerere. Această funcție are doi parametri: request (conține datele cererii) și response (gestionează trimiterea răspunsului).

În funcția-handler cu ajutorul proprietății request.url putem obține calea către resursa la care se face cererea. Trebuie să procesăm cererile către paginile "index.html" și "home.html" (și în perspectivă către orice alte pagini html). Calea începe întotdeauna cu slash "/". De exemplu, cererea către pagina "home.html" va reprezenta calea "/home.html". Prin urmare, pentru a obține din calea cerută calea către fișierele de pe discul dur, trebuie să eliminăm slash-ul inițial:

let filePath = request.url.substring(1);

Cu toate acestea, dacă cererea este adresată rădăcinii site-ului, atunci calea constă doar dintr-un singur slash "/". Prin urmare, dacă eliminăm acest slash, obținem un șir gol. Astfel, dacă cererea este adresată rădăcinii aplicației web, vom considera că cererea este adresată paginii principale - index.html:

if(!filePath) filePath = "index.html";

Și deoarece în cazul nostru răspunsul serverului va reprezenta codul html, atunci cu ajutorul metodei setHeader() stabilim pentru antetul "Content-Type" valoarea "text/html":

response.setHeader("Content-Type", "text/html; charset=utf-8;");

Adică răspunsul serverului va reprezenta html.

Apoi, cu ajutorul funcției fs.readFile citim fișierul către care se face cererea. Primul parametru al funcției - adresa fișierului (în acest caz se presupune că fișierul se află în aceeași folder cu fișierul serverului server.js). Al doilea parametru - funcția care este apelată după citirea fișierului și primește conținutul său prin al doilea parametru data. Este foarte posibil ca fișierul cerut să nu existe, și în acest caz trimitem eroarea 404:

fs.readFile(filePath, (error, data)=>{
   if(error){
       response.statusCode = 404;
       response.end("<h1>Resursa nu a fost găsită!</h1>");
   }

Dacă nu există erori, fișierul este găsit și citit cu succes, atunci trimitem parametrul data, care conține datele fișierului:

else{
   response.end(data);
}

La sfârșit, cu ajutorul funcției listen() lansăm serverul web pe portul 3000. Adică serverul va fi lansat la adresa http://localhost:3000/

Lansarea și testarea aplicației

Acum în consolă vom trece la folderul serverului cu ajutorul comenzii cd și vom lansa serverul cu ajutorul comenzii node server.js:

C:\app>node server.js
Serverul a fost lansat la adresa http://localhost:3000

După lansarea serverului, putem accesa în browser adresa http://localhost:3000, unde ne va fi afișată pagina, în codul javascript al căreia va fi creat un web worker. Acest web worker va executa sarcina definită în fișierul worker.js, iar pe consolă vom vedea rezultatul acestei lucrări.

Restricții ale web workerului

În exemplul de mai sus, în codul web workerului a fost utilizat un temporizator creat cu funcția setInterval(). Totuși, nu toate funcționalitățile JavaScript-ului standard de browser pot fi utilizate în sarcinile web workerului. Astfel, web workerii nu au acces la DOM și la obiectul window.

Cu toate acestea, o parte din funcționalitățile obiectului window (proprietăți și metode) sunt accesibile pentru web worker (ca în cazul funcției setInterval()). În special, următoarele funcții sunt accesibile:

  • atob()
  • btoa()
  • clearInterval()
  • clearTimeout()
  • queueMicrotask()
  • setInterval()
  • setTimeout()
  • structuredClone()
  • requestAnimationFrame() (doar pentru web workerii dedicați)
  • cancelAnimationFrame() (doar pentru web workerii dedicați)

De asemenea, pentru web workeri sunt accesibile următoarele proprietăți ale obiectului window:

  • console
  • location
  • navigator
  • indexDB

În plus, web workerii pot folosi următoarele API-uri:

  • Barcode Detection API
  • Broadcast Channel API
  • Cache API
  • Channel Messaging API
  • Console API
  • Web Crypto API (de exemplu, Crypto)
  • CSS Font Loading API
  • CustomEvent
  • Encoding API (de exemplu, TextEncoder, TextDecoder)
  • Fetch API
  • FileReader
  • FormData
  • ImageBitmap
  • ImageData
  • IndexedDB
  • Media Source Extensions API
  • Network Information API
  • Notifications API
  • OffscreenCanvas (și API pentru lucrul cu contextul elementului canvas)
  • Performance API
  • Server-sent events
  • ServiceWorkerRegistration
  • URL API
  • WebCodecs_API
  • WebSocket
  • XMLHttpRequest

Obținerea web workerului și self

Utilizând cuvântul self în scriptul web workerului (worker.js), ne putem referi la obiectul web workerului:

console.log(self); // obținem date despre web worker

let result = 1;
const intervalID = setInterval(work, 1000);

function work() {
   result = result * 2;
   console.log("result=", result);
   if(result>= 32) clearInterval(intervalID);
}

Oprirea funcționării web workerului

Un web worker poate funcționa pentru o perioadă lungă de timp, chiar până la infinit, cât timp utilizatorul se află pe pagină. Și poate apărea întrebarea cum să încheiem execuția web workerului. Pentru acest scop, interfața Worker definește metoda terminate(). De exemplu, să modificăm codul paginii web index.html în următorul mod:

<!DOCTYPE html>
<html>
<head>
   <meta charset="utf-8" />
   <title>FDC.COM</title>
</head>
<body>
<button id="btn">Stop</button>
<script>
const worker = new Worker("worker.js");
// la apăsarea butonului, oprirea funcționării web workerului
document.getElementById("btn").addEventListener("click", ()=> {
   worker.terminate();
   console.log("web worker stopped");
});
</script>
</body>
</html>

Aici, pe pagina web este definit un buton, la apăsarea căruia se produce oprirea web workerului.