de Creierul flămând

Lucrătorii web în acțiune: de ce sunt de ajutor și cum ar trebui să le folosiți

Lucratorii web in actiune de ce sunt de ajutor si
Fotografie de Fabian Grohs pe Unsplash

Javascript are un singur fir și mai multe scripturi nu pot fi executate în același timp. Deci, dacă executăm orice sarcină de calcul grea, uneori pagina noastră nu mai răspunde și utilizatorul nu poate face nimic altceva până când acea execuție nu este finalizată.

De exemplu:

average = (numbers) => {
    let startTime = new Date().getTime();
    let len = numbers,
        sum = 0,
        i;

    if (len === 0) {
        return 0;
    }

    for (i = 0; i < len; i++) {
        console.log('i :: ', i)
        sum += i;
    }

    let endTime = new Date().getTime();
    alert('Average - ', sum / len);
}

hello = () => {
    alert("Hello World !!");
}

/*
Paste the above code in browser dev tool console
and try to call average(10000) and hello one by one
*/
cod de blocare

În exemplul de mai sus, dacă sunați in medie inainte de Salut metoda, atunci pagina dvs. nu va mai răspunde și nu veți putea face clic pe Salut până la executarea in medie devine completat.

Puteți vedea asta când in medie este apelat cu 10000 ca intrare mai întâi, a durat ~ 1,82 secunde. Pentru acea perioadă de timp, pagina nu mai răspunde și nu ați putut să faceți clic pe butonul Bună ziua.

ad-banner

Programare asincronă

Javascript oferă calea dezvoltatorilor de a scrie cod asincron. Scriind cod asincron, puteți evita acest tip de problemă în aplicație, deoarece permite interfața de utilizare a aplicației dvs. să fie receptivă, „planificând” părți ale codului să fie executate puțin mai târziu în bucla evenimentului.

Un bun exemplu de programare asincronă este XHR request, în aceasta am lovit un API asincron și în timp ce așteptăm răspunsul, se poate executa alt cod. Dar acest lucru este limitat la anumite cazuri de utilizare legate în principal de API-urile web.

Un alt mod de scriere a codului asincron este prin utilizarea setTimeout metodă. În unele cazuri, puteți obține rezultate bune în deblocarea interfeței de utilizare din calculele care rulează mai mult folosind setTimeout. De exemplu, prin loturi de calcule complexe separate setTimeout apeluri.

De exemplu:

average = (numbers) => {
    let startTime = new Date().getTime();
    var len = numbers,
        sum = 0,
        i;

    if (len === 0) {
        return 0;
    }

    let calculateSumAsync = (i) => {
        if (i < len) {
            // Put the next function call on the event loop.
            setTimeout(() => {
                sum += i;
                calculateSumAsync(i + 1);
            }, 0);
        } else {
            // The end of the array is reached so we're invoking the alert.
            let endTime = new Date().getTime();
            alert('Average - ', sum / len);
        }
    };

    calculateSumAsync(0);
};

hello = () => {
    alert('Hello World !!')
};
programare asincronizată utilizând setTimeout

În acest exemplu, puteți vedea asta după ce faceți clic pe Calculați media , puteți face clic pe butonul Salut (care la rândul său afișează un mesaj de alertă). Acest mod de programare este cu siguranță non-blocant, dar necesită prea mult timp și nu este fezabil în aplicațiile din lumea reală.

Aici, pentru aceeași intrare 10000, a durat ~ 60 de secunde, ceea ce este foarte ineficient.

Deci, cum rezolvăm eficient aceste tipuri de probleme?

Raspunsul este Lucrători web.

Ce sunt lucrătorii web?

Lucrătorii web din Javascript sunt o modalitate excelentă de a executa o sarcină care este foarte laborioasă și care ia timp într-un fir separat de firul principal. Acestea rulează în fundal și efectuează sarcini fără a interfera cu interfața cu utilizatorul.

Web Workers nu fac parte din JavaScript, sunt o funcție de browser care poate fi accesată prin JavaScript.

Lucrătorii web sunt creați de o funcție constructor Muncitor() care rulează un fișier JS numit.

// create a dedicated web worker
const myWorker = new Worker('worker.js');

Dacă fișierul specificat există, atunci acesta va fi descărcat asincron și dacă nu, atunci lucrătorul va eșua în tăcere, astfel încât aplicația dvs. va funcționa în continuare în cazul 404.

Vom afla mai multe despre crearea și funcționarea lucrătorilor web în secțiunea următoare.

Firul de lucru are propriul context și, prin urmare, puteți accesa doar caracteristicile selectate în interiorul unui fir de lucru, cum ar fi – socketuri web, DB indexat.

Sunt cateva restricții cu lucrătorii web –

  1. Nu puteți manipula direct DOM din interiorul unui lucrător.
  2. Nu puteți utiliza unele metode și proprietăți implicite ale obiectului fereastră, deoarece obiectul fereastră nu este disponibil în interiorul unui fir de lucru.
  3. Contextul din interiorul firului de lucru poate fi accesat prin DedicatedWorkerGlobalScope sau SharedWorkerGlobalScope în funcție de utilizare.

Caracteristicile lucrătorilor web

Există două tipuri de lucrători web –

  1. Lucrător web dedicat – Un lucrător dedicat este accesibil doar prin scriptul care l-a numit.
  2. Lucrător web partajat – Un lucrător partajat este accesibil prin mai multe scripturi – chiar dacă acestea sunt accesate de ferestre diferite, iframe sau chiar lucrători.

Să discutăm mai multe despre aceste două tipuri de lucrători web –

Crearea unui lucrător web

Creația este cam aceeași atât pentru un angajat web dedicat, cât și pentru partajat.

Lucrător web dedicat

  • Crearea unui lucrător nou este simplă, trebuie doar să apelați constructorul Lucrător și treceți calea scriptului pe care doriți să îl executați ca lucrător.
// create a dedicated web worker
const myWorker = new Worker('worker.js');
main.js

Lucrător web partajat:

  • Crearea unui nou lucrător partajat este aproape la fel ca cea a lucrătorului dedicat, dar cu un nume de constructor diferit.
// creating a shared web worker
const mySharedWorker = new SharedWorker('worker.js');
shared-main.js

Comunicarea între firul principal și cel al lucrătorului

Comunicarea între firul principal și firul lucrător are loc prin postMessage metoda și onmessage organizatorul evenimentului.

Lucrător web dedicat
În cazul unui lucrător dedicat web, sistemul de comunicare este simplu. Trebuie doar să utilizați metoda postMessage ori de câte ori doriți să trimiteți un mesaj lucrătorului.

(() => {
  // new worker
  let myWorker = new Worker('worker.js');

  // event handler to recieve message from worker
  myWorker.onmessage = (e) => {
    document.getElementById('time').innerHTML = `${e.data.time} seconds`;
  };

  let average = (numbers) => {
    // sending message to web worker with an argument
    myWorker.postMessage(numbers);
  }

  average(1000);
})();
main.js

Și în interiorul unui web worker puteți răspunde când mesajul este primit scriind un bloc de gestionare a evenimentelor astfel:

onmessage = (e) => {
    let numbers = e.data;
    let startTime = new Date().getTime();
    let len = numbers,
        sum = 0,
        i;

    if (len === 0) {
        return 0;
    }

    for (i = 0; i < len; i++) {
        sum += i;
    }

    let endTime = new Date().getTime();
    postMessage({average: sum / len, time: ((endTime - startTime) / 1000)})
};
main-worker.js

onmessage handler permite să ruleze un anumit cod ori de câte ori este primit un mesaj.

Aici calculăm media numerelor și apoi folosim postMessage() din nou, pentru a posta rezultatul înapoi la firul principal.

După cum puteți vedea mai departe linia 6 în main.js am folosit evenimentul onmessage pe instanța lucrătorului. Deci, de fiecare dată când firul lucrător folosește postMessage, este declanșat mesajul din firul principal.

  • Lucrător web partajat
    În cazul unui lucrător web partajat, sistemul de comunicare este puțin diferit. Deoarece un lucrător este împărțit între mai multe scripturi, trebuie să comunicăm prin portul obiect al instanței de lucrător. Acest lucru se face implicit în cazul lucrătorilor dedicați. Trebuie să utilizați metoda postMessage ori de câte ori doriți să trimiteți un mesaj lucrătorului.
(() => {
  // new worker
  let myWorker = new Worker('worker.js');

  // event handler to recieve message from worker
  myWorker.onmessage = (e) => {
    document.getElementById('time').innerHTML = `${e.data.time} seconds`;
  };

  let average = (numbers) => {
    // sending message to web worker with an argument
    myWorker.postMessage(numbers);
  }

  average(1000);
main-shared.js

În interiorul unui lucrător web (main-shared-worker.js) este puțin complex. În primul rând, folosim un onconnect handler pentru a declanșa codul atunci când are loc o conexiune la port (randul 2).
Noi folosim ports atributul acestui obiect eveniment pentru a apuca portul și a-l stoca într-o variabilă (linia 4).
Apoi, adăugăm un message handler de pe port pentru a face calculul și a întoarce rezultatul la firul principal (linia 7 și linia 25) asa:

onmessage = (e) => {
    let numbers = e.data;
    let startTime = new Date().getTime();
    let len = numbers,
        sum = 0,
        i;

    if (len === 0) {
        return 0;
    }

    for (i = 0; i < len; i++) {
        sum += i;
    }

    let endTime = new Date().getTime();
    postMessage({average: sum / len, time: ((endTime - startTime) / 1000)})
};
main-shared-worker.js

Încetarea unui lucrător web

Dacă trebuie să revocați imediat un lucrător care rulează din firul principal, puteți face acest lucru apelând lucrătorul termina metodă:

// terminating a web worker instance
myWorker.terminate();

Firul muncitorului este ucis imediat fără posibilitatea de a-și finaliza operațiunile.

Creșterea lucrătorului web

Muncitorii pot genera mai mulți muncitori dacă doresc. Dar trebuie să fie găzduite în aceeași origine ca și pagina părinte.

Importarea scripturilor

Firele de lucru au acces la o funcție globală, importScripts(), care le permite să importe scripturi.

importScripts();                         /* imports nothing */
importScripts('foo.js');                 /* imports just "foo.js" */
importScripts('foo.js', 'bar.js');       /* imports two scripts */
importScripts('//example.com/hello.js'); /* You can import scripts from other origins */

Demo de lucru

Am discutat câteva dintre abordările de mai sus pentru a realiza o programare asincronizată, astfel încât interfața noastră de utilizare să nu fie blocată din cauza oricărei sarcini de calcul grele. Dar există unele limitări ale acestor abordări. Așadar, putem folosi lucrătorii web pentru a rezolva eficient acest tip de probleme.

Clic aici pentru a rula acest demo live.

Aici, veți vedea 3 secțiuni:

  1. Cod de blocare:
    Când faceți clic pe calculați media, încărcătorul nu se afișează și după ceva timp vedeți rezultatul final și timpul necesar. Acest lucru se datorează faptului că imediat ce metoda medie este chemat, am declanșat showLoader metoda de asemenea. Dar, din moment ce JS are un singur thread, nu va executa showLoader până când nu se termină executarea mediei. Deci, nu veți putea vedea încărcătorul în acest caz vreodată.
  2. Cod asincron:
    În acest sens am încercat să realizez aceeași funcționalitate folosind metoda setTimeout și punând fiecare funcție de execuție într-o buclă de evenimente. Veți vedea încărcătorul în acest caz, dar răspunsul durează timp în comparație cu metoda definită mai sus.
  3. Lucrător web:
    Acesta este un exemplu de utilizare a unui web worker. În aceasta veți vedea încărcătorul imediat ce faceți clic pe calculați media și veți obține un răspuns în același timp cu metoda 1, pentru același număr.

Puteți accesa codul sursă pentru același lucru aici.

Concepte avansate

Există câteva concepte avansate legate de lucrătorii web. Nu le vom discuta în detaliu, dar este bine să știm despre ele.

  1. Politica de securitate a conținutului –
    Lucrătorii web au propriul context de execuție independent de documentul care le-a creat și, din acest motiv, nu sunt reglementați de Politica de securitate a conținutului firului / lucrătorului părinte.
    Excepția de la aceasta este dacă originea scriptului de lucru este un identificator unic la nivel global (de exemplu, dacă adresa URL are o schemă de date sau blob). În acest caz, lucrătorul moștenește politica de securitate a conținutului documentului sau lucrătorul care l-a creat.
  2. Transferul de date către și de la lucrători
    Datele transmise între firul principal și cel de lucru sunt copiat și nepartajat. Obiectele sunt serializate pe măsură ce sunt predate lucrătorului și, ulterior, sunt dez serializate la celălalt capăt. Pagina și lucrătorul nu partajați aceeași instanță, deci rezultatul final este acela un duplicat este creat la fiecare capăt.
    Browsere implementate Clonarea structurată algoritm pentru a realiza acest lucru.
  3. Lucrători încorporați –
    De asemenea, puteți încorpora codul lucrătorului într-o pagină web (html). Pentru aceasta, trebuie să adăugați o etichetă de script fără un atribut src și să îi atribuiți un tip MIME neexecutabil, astfel:
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <title>embedded worker</title>
  <!--script tag with un-identified MIME and w/o src attr -->
  <script type="text/js-worker">
    // This script WON'T be parsed by JS engines because its MIME type is text/js-worker.
    var myVar="Hello World!";
    
    // worker block
    function onmessage(e) {
      // worker code
    }
  </script>
</head>
<body></body>
</html>

Există o mulțime de cazuri de utilizare pentru a utiliza lucrătorii web în aplicația noastră. Tocmai am discutat un mic scenariu. Sper că acest lucru vă ajută să înțelegeți conceptul de lucrători web.

Github Repo: https://github.com/bhushangoel/webworker-demo-1 Lucrător web în acțiune: https://bhushangoel.github.io/webworker-demo-1/Vitrina demo JS: https://bhushangoel.github.io/

Mulțumesc că ai citit.

Învățare fericită 🙂

Publicat inițial la www.thehungrybrain.com.