Web-Worker in Aktion: Warum sie hilfreich sind und wie Sie sie verwenden sollten

Javascript ist Single-Threaded und mehrere Skripte können nicht gleichzeitig ausgeführt werden. Wenn wir also eine schwere Rechenaufgabe ausführen, reagiert unsere Seite manchmal nicht mehr und der Benutzer kann nichts anderes tun, bis diese Ausführung abgeschlossen ist.

Zum Beispiel:

average = (numbers) => { let startTime = new Date().getTime(); let len = numbers, sum = 0, i; if (len === 0) { return 0; } for (i = 0; i  { alert("Hello World !!"); } /* Paste the above code in browser dev tool console and try to call average(10000) and hello one by one */

Wenn Sie im obigen Beispiel den Durchschnitt vor der Hallo- Methode aufrufen, reagiert Ihre Seite nicht mehr und Sie können erst dann auf Hallo klicken, wenn die Ausführung des Durchschnitts abgeschlossen ist.

Sie können sehen, dass es ~ 1,82 Sekunden dauerte , wenn der Durchschnitt zuerst mit 10000 als Eingabe aufgerufen wurde. Während dieser Zeit reagiert die Seite nicht mehr und Sie konnten nicht auf die Schaltfläche "Hallo" klicken.

Asynchrone Programmierung

Javascript bietet Entwicklern die Möglichkeit, asynchronen Code zu schreiben . Durch das Schreiben von asynchronem Code können Sie diese Art von Problemen in Ihrer Anwendung vermeiden, da die Benutzeroberfläche Ihrer App darauf reagieren kann, indem Teile des Codes etwas später in der Ereignisschleife „geplant“ werden.

Ein gutes Beispiel für XHR requestasynchrone Programmierung ist , dass wir asynchron auf eine API zugreifen und während wir auf die Antwort warten, anderer Code ausgeführt werden kann. Dies ist jedoch hauptsächlich auf bestimmte Anwendungsfälle im Zusammenhang mit Web-APIs beschränkt.

Eine andere Möglichkeit, asynchronen Code zu schreiben, ist die Verwendung von setTimeoutMethode. In einigen Fällen können Sie gute Ergebnisse erzielen, indem Sie die Benutzeroberfläche für die Freigabe von länger laufenden Berechnungen freigeben setTimeout. Zum Beispiel durch Stapeln einer komplexen Berechnung in separaten setTimeoutAufrufen.

Zum Beispiel:

average = (numbers) => { let startTime = new Date().getTime(); var len = numbers, sum = 0, i; if (len === 0) { return 0; } let calculateSumAsync = (i) => { if (i  { 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 !!') };

In diesem Beispiel sehen Sie, dass Sie nach dem Klicken auf die Schaltfläche Durchschnitt berechnen immer noch auf die Schaltfläche Hallo klicken können (die wiederum eine Warnmeldung anzeigt). Diese Art der Programmierung ist sicherlich nicht blockierend, nimmt jedoch zu viel Zeit in Anspruch und ist in realen Anwendungen nicht möglich.

Hier dauerte es für denselben Eingang 10000 ~ 60 Sekunden, was sehr ineffizient ist.

Wie lösen wir diese Probleme effizient?

Die Antwort lautet Web Workers.

Was sind Webworker?

Web-Worker in Javascript sind eine großartige Möglichkeit, eine sehr mühsame und zeitaufwändige Aufgabe in einem vom Haupt-Thread getrennten Thread auszuführen. Sie werden im Hintergrund ausgeführt und führen Aufgaben aus, ohne die Benutzeroberfläche zu beeinträchtigen.

Web Worker sind nicht Teil von JavaScript, sondern eine Browserfunktion, auf die über JavaScript zugegriffen werden kann.

Web-Worker werden von einer Konstruktorfunktion Worker () erstellt, die eine benannte JS-Datei ausführt.

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

Wenn die angegebene Datei vorhanden ist, wird sie asynchron heruntergeladen, und wenn nicht, schlägt der Worker unbeaufsichtigt fehl, sodass Ihre Anwendung im Fall von 404 weiterhin funktioniert.

Im nächsten Abschnitt erfahren Sie mehr über das Erstellen und Arbeiten von Web-Workern.

Der Worker-Thread hat einen eigenen Kontext. Daher können Sie nur auf ausgewählte Features innerhalb eines Worker-Threads zugreifen, z. B. Web-Sockets, indizierte Datenbank.

Es gibt einige Einschränkungen bei Webarbeitern -

  1. Sie können das DOM nicht direkt von einem Mitarbeiter aus bearbeiten.
  2. Sie können einige Standardmethoden und -eigenschaften des Fensterobjekts nicht verwenden, da das Fensterobjekt in einem Arbeitsthread nicht verfügbar ist.
  3. Auf den Kontext innerhalb des Worker-Threads kann je nach Verwendung über DedicatedWorkerGlobalScope oder SharedWorkerGlobalScope zugegriffen werden .

Funktionen von Web Workern

Es gibt zwei Arten von Web-Workern:

  1. Dedizierter Web-Worker - Auf einen dedizierten Worker kann nur über das Skript zugegriffen werden, das ihn aufgerufen hat.
  2. Shared Web Worker - Auf einen Shared Worker kann über mehrere Skripte zugegriffen werden - auch wenn auf sie über verschiedene Fenster, Iframes oder sogar Worker zugegriffen wird.

Lassen Sie uns mehr über diese beiden Arten von Web-Workern diskutieren -

Erstellung eines Web Workers

Die Erstellung ist für einen dedizierten und einen gemeinsam genutzten Web-Worker ziemlich gleich.

Engagierter Web-Worker

  • Das Erstellen eines neuen Workers ist einfach. Rufen Sie einfach den Worker-Konstruktor auf und übergeben Sie den Pfad des Skripts, das Sie als Worker ausführen möchten.
// create a dedicated web worker const myWorker = new Worker('worker.js');

Geteilter Web-Worker:

  • Das Erstellen eines neuen freigegebenen Workers entspricht weitgehend dem eines dedizierten Workers, jedoch mit einem anderen Konstruktornamen.
// creating a shared web worker const mySharedWorker = new SharedWorker('worker.js');

Kommunikation zwischen Haupt- und Arbeitsthread

Die Kommunikation zwischen Hauptthread und Arbeitsthread erfolgt über die postMessage- Methode und den onmessage- Ereignishandler.

Engagierter Web-Worker

Bei einem dedizierten Web-Worker ist das Kommunikationssystem einfach. Sie müssen nur die postMessage-Methode verwenden, wenn Sie eine Nachricht an den Worker senden möchten.

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

In einem Web-Worker können Sie auf den Empfang der Nachricht antworten, indem Sie einen Ereignishandlerblock wie folgt schreiben:

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

Der onmessageHandler ermöglicht das Ausführen von Code, wenn eine Nachricht empfangen wird.

Hier berechnen wir den Durchschnitt der Zahlen und verwenden ihn dann postMessage()erneut, um das Ergebnis wieder im Haupt-Thread zu veröffentlichen.

Wie Sie in Zeile 6 in main.js sehen können , haben wir das Ereignis onmessage für die Worker-Instanz verwendet. Wenn also ein Arbeitsthread postMessage verwendet, wird eine Nachricht im Hauptthread ausgelöst.

  • Geteilter Web-Worker

    Bei einem gemeinsam genutzten Web-Worker unterscheidet sich das Kommunikationssystem kaum. Da ein Worker von mehreren Skripten gemeinsam genutzt wird, müssen wir über das Portobjekt der Worker-Instanz kommunizieren. Dies geschieht implizit bei engagierten Arbeitnehmern. Sie müssen die postMessage-Methode verwenden, wenn Sie eine Nachricht an den Worker senden möchten.

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

In einem Web-Worker ( main-shared-worker.js ) ist es ein wenig komplex. Zuerst verwenden wir einen onconnectHandler, um Code auszulösen, wenn eine Verbindung zum Port hergestellt wird ( Zeile 2 ).

Wir verwenden das portsAttribut dieses Ereignisobjekts, um den Port zu erfassen und in einer Variablen zu speichern ( Zeile 4 ).

Als Nächstes fügen wir messagedem Port einen Handler hinzu, um die Berechnung durchzuführen und das Ergebnis wie folgt an den Hauptthread ( Zeile 7 und Zeile 25 ) zurückzugeben:

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

Termination of a web worker

If you need to immediately terminate a running worker from the main thread, you can do so by calling the worker’s terminate method:

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

The worker thread is killed immediately without an opportunity to complete its operations.

Spawning of web worker

Workers may spawn more workers if they wish. But they must be hosted within the same origin as the parent page.

Importing Scripts

Worker threads have access to a global function, importScripts(), which lets them import scripts.

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 */

Working Demo

We have discussed some of the approaches above to achieve async programming so that our UI doesn’t get blocked due to any heavy computational task. But there are some limitations to those approaches. So we can use web workers to solve these kind of problems efficiently.

Click here to run this live demo.

Here, you will see 3 sections:

  1. Blocking Code:

    When you click on calculate average, the loader does not display and after some time you see the final result and time taken. This is because as soon as the average method gets called, I have triggered the showLoader method also. But since JS is single threaded, it won’t execute showLoader until the execution of average gets completed. So, you won’t be able to see the loader in this case ever.

  2. Async Code:

    In this I tried to achieve the same functionality by using the setTimeout method and putting every function execution into an event loop. You will see the loader in this case, but the response takes time as compared to the method defined above.

  3. Web worker:

    This is an example of using a web worker. In this you will see the loader as soon as you click on calculate average and you will get a response in the same time as of method 1, for the same number.

You can access the source code for the same here.

Advanced concepts

There are some advanced concepts related to web workers. We won’t be discussing them in detail, but its good to know about them.

  1. Content Security Policy —

    Web workers have their own execution context independent of the document that created them and because of this reason they are not governed by the Content Security Policy of the parent thread/worker.

    The exception to this is if the worker script's origin is a globally unique identifier (for example, if its URL has a scheme of data or blob). In this case, the worker inherit the content security policy of the document or worker that created it.

  2. Transferring data to and from workers

    Data passed between main and worker thread is copied and not shared. Objects are serialized as they're handed to the worker, and subsequently, de-serialized on the other end. The page and worker do not share the same instance, so the end result is that a duplicate is created on each end.

    Browsers implemented Structured Cloning algorithm to achieve this.

  3. Embedded workers —

    You can also embed the code of worker inside a web page (html). For this you need to add a script tag without a src attribute and assign a non-executable MIME type to it, like this:

    embedded 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 }    

There are a lot of use cases to use web workers in our application. I have just discussed a small scenario. Hope this helps you understand the concept of web workers.

[Links]

Github Repo : //github.com/bhushangoel/webworker-demo-1 Web worker in action : //bhushangoel.github.io/webworker-demo-1/JS demo showcase : //bhushangoel.github.io/

Thank you for reading.

Happy Learning :)

Originally published at www.thehungrybrain.com.