Articles

Filetarea web-ului cu lucrătorii modulului

JavaScript este un singur fir, ceea ce înseamnă că poate efectua o singură operație la un moment dat. Acest lucru este intuitiv și funcționează bine pentru o mulțime de cazuri de pe web, dar poate deveni problematic atunci când trebuie să facem sarcini grele de ridicare, cum ar fi prelucrarea datelor, analiza, calculul sau analiza. Pe măsură ce aplicațiile din ce în ce mai complexe sunt livrate pe web, există o nevoie crescută de procesare multi-threaded.

pe platforma web, primitivul principal pentru filetare și paralelism este API-ul web Workers. Lucrătorii sunt o abstracție ușor pe partea de sus a fire de sistem de operare care expun un mesaj care trece API pentru comunicare inter-thread. Acest lucru poate fi extrem de util atunci când efectuați calcule costisitoare sau operați pe seturi de date mari, permițând firului principal să ruleze fără probleme în timp ce efectuați operațiunile scumpe pe unul sau mai multe fire de fundal.

Iată un exemplu tipic de utilizare lucrător, în cazul în care un script lucrător ascultă pentru mesajele din firul principal și răspunde prin trimiterea înapoi mesaje proprii:

pagina.js:

const worker = new Worker('worker.js');
worker.addEventListener(e => {
console.log(e.data);
});
worker.postMessage('hello');

lucrător.js:

addEventListener('message', e => {
if (e.data === 'hello') {
postMessage('world');
}
});

API-ul web Worker este disponibil în majoritatea browserelor de peste zece ani. În timp ce asta înseamnă că lucrătorii au un suport excelent pentru browser și sunt bine optimizați, înseamnă, de asemenea, că sunt precedați de modulele JavaScript. Deoarece nu exista un sistem de module atunci când lucrătorii au fost proiectați, API-ul pentru încărcarea codului într-un lucrător și scrierea scripturilor a rămas similar cu abordările sincrone de încărcare a scripturilor comune în 2009.

istoric: muncitori clasici #

constructorul muncitor ia un URL de script clasic, care este relativ la URL-ul documentului. Returnează imediat o referință la noua instanță a lucrătorului, care expune o interfață de mesagerie, precum și o metodă terminate() care oprește imediat și distruge lucrătorul.

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

animportScripts() funcția este disponibilă în Web workers pentru încărcarea codului suplimentar, dar întrerupe executarea lucrătorului pentru a prelua și evalua fiecare script. De asemenea, execută scripturi în domeniul global ca o etichetă clasică <script>, ceea ce înseamnă că variabilele dintr-un script pot fi suprascrise de variabilele din altul.

lucrător.js:

importScripts('greet.js');
// ^ could block for seconds
addEventListener('message', e => {
postMessage(sayHello());
});

salut.js:

// global to the whole worker
function sayHello() {
return 'world';
}

Din acest motiv, lucrătorii web au impus istoric un efect supradimensionat asupra arhitecturii unei aplicații. Dezvoltatorii au fost nevoiți să creeze instrumente și soluții inteligente pentru a face posibilă utilizarea lucrătorilor web fără a renunța la practicile moderne de dezvoltare. De exemplu, bundlers precum webpack încorporează o implementare mică a încărcătorului de module în codul generat care folosește importScripts() pentru încărcarea codului, dar înfășoară modulele în funcții pentru a evita coliziunile variabile și pentru a simula importurile și exporturile de dependență.

introduceți lucrătorii modulului #

Un nou mod pentru lucrătorii web cu avantajele ergonomice și de performanță ale modulelor JavaScript este livrat în Chrome 80, numit lucrătorii modulului. Worker constructorul acceptă acum o nouă opțiune{type:"module"}, care modifică încărcarea și execuția scriptului pentru a se potrivi<script type="module">.

const worker = new Worker('worker.js', {
type: 'module'
});

deoarece lucrătorii modulului sunt module JavaScript standard, pot utiliza declarații de import și export. Ca și în cazul tuturor modulelor JavaScript, dependențele sunt executate o singură dată într-un context dat (fir principal, lucrător etc.), iar toate importurile viitoare fac referire la instanța modulului deja executată. Încărcarea și executarea modulelor JavaScript este, de asemenea, optimizată de browsere. Dependențele unui modul pot fi încărcate înainte ca modulul să fie executat, ceea ce permite încărcarea în paralel a arborilor de module întregi. Modulul de încărcare cache, de asemenea, codul analizat, ceea ce înseamnă module care sunt utilizate pe firul principal și într-un lucrător trebuie doar să fie analizat o singură dată.

trecerea la modulele JavaScript permite, de asemenea, utilizarea importului dinamic pentru codul de încărcare leneș fără a bloca executarea lucrătorului. Importul dinamic este mult mai explicit decât utilizarea importScripts() pentru a încărca dependențe, deoarece exporturile modulului importat sunt returnate mai degrabă decât să se bazeze pe variabile globale.

lucrător.js:

import { sayHello } from './greet.js';
addEventListener('message', e => {
postMessage(sayHello());
});

salut.js:

import greetings from './data.js';
export function sayHello() {
return greetings.hello;
}

pentru a asigura o performanță excelentă, vechea metodăimportScripts() nu este disponibilă în lucrătorii modulului. Comutarea lucrătorilor pentru a utiliza module JavaScript înseamnă că tot codul este încărcat în modul strict. O altă modificare notabilă este că valoarea thisîn domeniul de aplicare de nivel superior al unui Modul JavaScript esteundefined, în timp ce la lucrătorii clasici valoarea este domeniul global al lucrătorului. Din fericire, a existat întotdeauna un self global care oferă o referință la domeniul global. Este disponibil în toate tipurile de lucrători, inclusiv lucrătorii de servicii, precum și în DOM.

lucrătorii modulului elimină, de asemenea, suportul pentru comentariile în stil HTML. Știați că puteți utiliza comentarii HTML în scripturile web worker?

preîncărcarea lucrătorilor cu modulepreload #

o îmbunătățire substanțială a performanței care vine cu lucrătorii modulului este capacitatea de a preîncărca lucrătorii și dependențele acestora. Cu lucrătorii modulului, scripturile sunt încărcate și executate ca module JavaScript standard, ceea ce înseamnă că pot fi preîncărcate și chiar pre-analizate folosind modulepreload:

<!-- preloads worker.js and its dependencies: -->
<link rel="modulepreload" href="worker.js">
<script>
addEventListener('load', () => {
// our worker code is likely already parsed and ready to execute!
const worker = new Worker('worker.js', { type: 'module' });
});
</script>

modulele preîncărcate pot fi utilizate atât de firul principal, cât și de lucrătorii modulului. Acest lucru este util pentru modulele care sunt importate în ambele contexte, sau în cazurile în care nu este posibil să se știe în avans dacă un modul va fi utilizat pe firul principal sau într-un lucrător.

anterior, opțiunile disponibile pentru preîncărcarea scripturilor web worker erau limitate și nu neapărat fiabile. Lucrătorii clasici aveau propriul tip de resursă” lucrător”pentru preîncărcare, dar niciun browser nu a implementat <link rel="preload" as="worker">. Ca urmare, tehnica principală disponibilă pentru preîncărcarea lucrătorilor web a fost utilizarea <link rel="prefetch">, care s-a bazat în întregime pe memoria cache HTTP. Atunci când este utilizat în combinație cu anteturile corecte de cache, acest lucru a făcut posibilă evitarea instanțierii lucrătorilor care trebuie să aștepte pentru a descărca scriptul lucrătorului. Cu toate acestea, spre deosebire de modulepreload această tehnică nu a acceptat dependențele de preîncărcare sau pre-parsare.

cum rămâne cu lucrătorii în comun? #

lucrătorii partajați au fost actualizați cu suport pentru modulele JavaScript începând cu Chrome 83. La fel ca lucrătorii dedicați, construirea unui lucrător partajat cu {type:"module"} opțiunea încarcă acum scriptul lucrătorului ca un modul mai degrabă decât un script clasic:

const worker = new SharedWorker('/worker.js', {
type: 'module'
});

înainte de a sprijini modulele JavaScript, SharedWorker() constructorul aștepta doar o adresă URL și o opțiune name argument. Acest lucru va continua să funcționeze pentru utilizarea clasică a lucrătorilor în comun; cu toate acestea, crearea modulului shared workers necesită utilizarea noului argument options. Opțiunile disponibile sunt aceleași cu cele pentru un lucrător dedicat, inclusiv name opțiune care înlocuiește argumentul anterior name.

ce zici de lucrător de serviciu? #

specificația service worker a fost deja actualizată pentru a sprijini acceptarea unui Modul JavaScript ca punct de intrare, folosind aceeași opțiune{type:"module"} ca lucrători de module, totuși această modificare nu a fost încă implementată în browsere. Odată ce se întâmplă acest lucru, va fi posibilă instanțierea unui lucrător de service folosind un Modul JavaScript folosind următorul cod:

navigator.serviceWorker.register('/sw.js', {
type: 'module'
});

acum că specificația a fost actualizată, browserele încep să implementeze noul comportament. Acest lucru necesită timp, deoarece există unele complicații suplimentare asociate cu aducerea modulelor JavaScript la service worker. Înregistrarea lucrătorilor de servicii trebuie să compare scripturile importate cu versiunile anterioare din cache atunci când determină dacă să declanșeze o actualizare, iar acest lucru trebuie implementat pentru modulele JavaScript atunci când este utilizat pentru lucrătorii de servicii. De asemenea, lucrătorii de servicii trebuie să poată ocoli memoria cache pentru scripturi în anumite cazuri atunci când verifică actualizările.