Verwalten lang andauernder Aufgaben in einer React-App mit Web-Workern

Veröffentlicht: 2022-03-10
Kurze Zusammenfassung ↬ In diesem Tutorial erfahren wir, wie Sie die Web Worker-API verwenden, um zeitaufwändige und die Benutzeroberfläche blockierende Aufgaben in einer JavaScript-App zu verwalten, indem wir eine Beispiel-Web-App erstellen, die Web Workers nutzt. Schließlich beenden wir den Artikel, indem wir alles in eine React-Anwendung übertragen.

Die Reaktionszeit ist eine große Sache, wenn es um Webanwendungen geht. Benutzer verlangen sofortige Antworten, ganz gleich, was Ihre App gerade macht. Ob es nur darum geht, den Namen einer Person anzuzeigen oder Zahlen zu knirschen, Benutzer von Web-Apps verlangen, dass Ihre App jedes Mal auf ihren Befehl reagiert. Angesichts der Single-Thread-Natur von JavaScript kann dies manchmal schwer zu erreichen sein. Aber in diesem Artikel erfahren wir, wie wir die Web Worker-API nutzen können, um ein besseres Erlebnis zu bieten.

Beim Schreiben dieses Artikels bin ich von folgenden Annahmen ausgegangen:

  1. Um Ihnen folgen zu können, sollten Sie zumindest etwas mit JavaScript und der Dokumenten-API vertraut sein;
  2. Sie sollten auch über praktische Kenntnisse von React verfügen, damit Sie ein neues React-Projekt mit Create React App erfolgreich starten können.

Wenn Sie weitere Einblicke in dieses Thema benötigen, habe ich im Abschnitt „Weitere Ressourcen“ eine Reihe von Links eingefügt, die Ihnen helfen, sich auf den neuesten Stand zu bringen.

Beginnen wir zunächst mit Web Workers.

Was ist ein Webworker?

Um Web Worker und das Problem, das sie lösen sollen, zu verstehen, muss man verstehen, wie JavaScript-Code zur Laufzeit ausgeführt wird. Zur Laufzeit wird JavaScript-Code sequentiell und turn-by-turn ausgeführt. Sobald ein Codeabschnitt endet, beginnt der nächste in der Reihe zu laufen und so weiter. In technischer Hinsicht sagen wir, dass JavaScript Single-Threaded ist. Dieses Verhalten impliziert, dass jeder nachfolgende Code warten muss, bis dieser Code seine Ausführung beendet hat, sobald ein Codeabschnitt ausgeführt wird. Somit „blockiert“ jede Codezeile die Ausführung von allem, was danach kommt. Es ist daher wünschenswert, dass jedes Stück Code so schnell wie möglich fertig ist. Wenn ein Teil des Codes zu lange braucht, um fertig zu werden, scheint unser Programm nicht mehr zu funktionieren. Im Browser manifestiert sich dies als eingefrorene, nicht reagierende Seite. In einigen extremen Fällen friert die Registerkarte vollständig ein.

Stellen Sie sich vor, Sie fahren auf einer Einspur. Wenn einer der Fahrer vor Ihnen aus irgendeinem Grund stehen bleibt, haben Sie einen Stau. Mit einem Programm wie Java könnte der Verkehr auf anderen Spuren weiterlaufen. Daher wird Java als multithreaded bezeichnet. Web Worker sind ein Versuch, JavaScript multithreaded zu machen.

Der folgende Screenshot zeigt, dass die Web Worker-API von vielen Browsern unterstützt wird, sodass Sie sich bei der Verwendung sicher fühlen sollten.

Diagramm zur Browserunterstützung für Webworker wird angezeigt
Browserunterstützung für Web Workers. (Große Vorschau)

Web Worker werden in Hintergrundthreads ausgeführt, ohne die Benutzeroberfläche zu stören, und sie kommunizieren über Ereignishandler mit dem Code, der sie erstellt hat.

Eine hervorragende Definition eines Web Workers stammt von MDN:

„Ein Worker ist ein Objekt, das mit einem Konstruktor erstellt wurde (z. B. Worker() , der eine benannte JavaScript-Datei ausführt – diese Datei enthält den Code, der im Worker-Thread ausgeführt wird; Worker werden in einem anderen globalen Kontext ausgeführt, der sich vom aktuellen window unterscheidet. Daher , verwenden Sie die window , um den aktuellen globalen Bereich abzurufen (anstelle von self innerhalb eines Worker wird ein Fehler zurückgegeben.

Ein Worker wird mit dem Worker -Konstruktor erstellt.

 const worker = new Worker('worker-file.js')

Mit einigen Ausnahmen ist es möglich, den meisten Code innerhalb eines Webworkers auszuführen. Beispielsweise können Sie das DOM nicht innerhalb eines Workers manipulieren. Es besteht kein Zugriff auf die document -API.

Worker und der Thread, der sie erzeugt, senden einander Nachrichten mithilfe der Methode postMessage() . In ähnlicher Weise reagieren sie mit dem onmessage Event-Handler auf Nachrichten. Es ist wichtig, diesen Unterschied zu bekommen. Das Senden von Nachrichten wird mit einer Methode erreicht; Der Empfang einer Nachricht erfordert einen Ereignishandler. Die empfangene Nachricht ist im data des Ereignisses enthalten. Ein Beispiel dafür werden wir im nächsten Abschnitt sehen. Aber lassen Sie mich kurz erwähnen, dass die Art von Arbeiter, über die wir gesprochen haben, als „engagierter Arbeiter“ bezeichnet wird. Das bedeutet, dass der Worker nur für das Skript zugänglich ist, das ihn aufgerufen hat. Es ist auch möglich, einen Worker zu haben, auf den von mehreren Skripts aus zugegriffen werden kann. Diese werden gemeinsam genutzte Worker genannt und mit dem SharedWorker Konstruktor erstellt, wie unten gezeigt.

 const sWorker = new SharedWorker('shared-worker-file.js')

Um mehr über Worker zu erfahren, lesen Sie bitte diesen MDN-Artikel. Der Zweck dieses Artikels besteht darin, Ihnen den Einstieg in die Verwendung von Webworkern zu erleichtern. Kommen wir dazu, indem wir die n-te Fibonacci-Zahl berechnen.

Mehr nach dem Sprung! Lesen Sie unten weiter ↓

Berechnung der N-ten Fibonacci-Zahl

Hinweis: Für diesen und die nächsten beiden Abschnitte verwende ich Live Server auf VSCode, um die App auszuführen. Du kannst sicherlich auch etwas anderes verwenden.

Dies ist der Abschnitt, auf den Sie gewartet haben. Wir werden endlich Code schreiben, um Web Workers in Aktion zu sehen. Nun, nicht so schnell. Wir würden die Arbeit eines Web Workers nicht zu schätzen wissen, wenn wir nicht auf die Art von Problemen stoßen, die er löst. In diesem Abschnitt sehen wir uns ein Beispielproblem an, und im folgenden Abschnitt sehen wir, wie uns ein Webworker hilft, es besser zu machen.

Stellen Sie sich vor, Sie entwickeln eine Web-App, mit der Benutzer die n-te Fibonacci-Zahl berechnen können. Falls Sie mit dem Begriff „Fibonacci-Zahl“ noch nicht vertraut sind, können Sie hier mehr darüber lesen, aber zusammenfassend sind Fibonacci-Zahlen eine Folge von Zahlen, bei der jede Zahl die Summe der beiden vorhergehenden Zahlen ist.

Mathematisch wird es ausgedrückt als:

Die ersten Zahlen der Folge lauten also:

1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 ...

In einigen Quellen beginnt die Sequenz bei F 0 = 0 , in diesem Fall gilt die folgende Formel für n > 1 :

In diesem Artikel beginnen wir bei F 1 = 1. Eine Sache, die wir sofort an der Formel sehen können, ist, dass die Zahlen einem rekursiven Muster folgen. Die Aufgabe besteht nun darin, eine rekursive Funktion zur Berechnung der n-ten Fibonacci-Zahl (FN) zu schreiben.

Nach ein paar Versuchen können Sie, glaube ich, leicht auf die folgende Funktion kommen.

 const fib = n => { if (n < 2) { return n // or 1 } else { return fib(n - 1) + fib(n - 2) } }

Die Funktion ist einfach. Wenn n kleiner als 2 ist, gib n (oder 1) zurück, andernfalls gib die Summe der n-1 und n-2 FNs zurück. Mit Pfeilfunktionen und ternärem Operator können wir uns einen Einzeiler einfallen lassen.

 const fib = n => (n < 2 ? n : fib(n-1) + fib(n-2))

Diese Funktion hat eine Zeitkomplexität von 0(2 n ) . Dies bedeutet einfach, dass mit zunehmendem Wert von n die zur Berechnung der Summe erforderliche Zeit exponentiell zunimmt. Dies führt zu einer sehr lang andauernden Aufgabe, die unsere Benutzeroberfläche bei großen Werten von n beeinträchtigen könnte. Sehen wir uns das in Aktion an.

Hinweis : Dies ist keineswegs der beste Weg, um dieses spezielle Problem zu lösen. Meine Entscheidung, diese Methode zu verwenden, dient dem Zweck dieses Artikels.

Erstellen Sie zunächst einen neuen Ordner und benennen Sie ihn nach Belieben. Erstellen Sie nun in diesem Ordner einen Ordner src/ . Erstellen Sie außerdem eine index.html -Datei im Stammordner. Erstellen Sie im Ordner src/ eine Datei namens index.js .

Öffnen Sie index.html und fügen Sie den folgenden HTML-Code hinzu.

 <!DOCTYPE html> <html> <head> <link rel="stylesheet" href="styles.css"> </head> <body> <div class="heading-container"> <h1>Computing the nth Fibonnaci number</h1> </div> <div class="body-container"> <p id='error' class="error"></p> <div class="input-div"> <input id='number-input' class="number-input" type='number' placeholder="Enter a number" /> <button id='submit-btn' class="btn-submit">Calculate</button> </div> <div id='results-container' class="results"></div> </div> <script src="/src/index.js"></script> </body> </html>

Dieser Teil ist sehr einfach. Zuerst haben wir eine Überschrift. Dann haben wir einen Container mit einer Eingabe und einer Schaltfläche. Ein Benutzer würde eine Zahl eingeben und dann auf „Berechnen“ klicken. Wir haben auch einen Container, um das Ergebnis der Berechnung zu halten. Zuletzt fügen wir die Datei src/index.js in ein script -Tag ein.

Sie können den Stylesheet-Link löschen. Aber wenn Sie wenig Zeit haben, habe ich einige CSS definiert, die Sie verwenden können. Erstellen Sie einfach die Datei styles.css im Stammordner und fügen Sie die folgenden Stile hinzu:

 body { margin: 0; padding: 0; box-sizing: border-box; } .body-container, .heading-container { padding: 0 20px; } .heading-container { padding: 20px; color: white; background: #7a84dd; } .heading-container > h1 { margin: 0; } .body-container { width: 50% } .input-div { margin-top: 15px; margin-bottom: 15px; display: flex; align-items: center; } .results { width: 50vw; } .results>p { font-size: 24px; } .result-div { padding: 5px 10px; border-radius: 5px; margin: 10px 0; background-color: #e09bb7; } .result-div p { margin: 5px; } span.bold { font-weight: bold; } input { font-size: 25px; } p.error { color: red; } .number-input { padding: 7.5px 10px; } .btn-submit { padding: 10px; border-radius: 5px; border: none; background: #07f; font-size: 24px; color: white; cursor: pointer; margin: 0 10px; }

Öffnen Sie jetzt src/index.js und entwickeln Sie es langsam. Fügen Sie den folgenden Code hinzu.

 const fib = (n) => (n < 2 ? n : fib(n - 1) + fib(n - 2)); const ordinal_suffix = (num) => { // 1st, 2nd, 3rd, 4th, etc. const j = num % 10; const k = num % 100; switch (true) { case j === 1 && k !== 11: return num + "st"; case j === 2 && k !== 12: return num + "nd"; case j === 3 && k !== 13: return num + "rd"; default: return num + "th"; } }; const textCont = (n, fibNum, time) => { const nth = ordinal_suffix(n); return ` <p id='timer'>Time: <span class='bold'>${time} ms</span></p> <p><span class="bold" id='nth'>${nth}</span> fibonnaci number: <span class="bold" id='sum'>${fibNum}</span></p> `; };

Hier haben wir drei Funktionen. Die erste ist die Funktion, die wir zuvor zur Berechnung des n-ten FN gesehen haben. Die zweite Funktion ist nur eine Hilfsfunktion, um einer Ganzzahl ein geeignetes Suffix hinzuzufügen. Die dritte Funktion nimmt einige Argumente und gibt ein Markup aus, das wir später in das DOM einfügen werden. Das erste Argument ist die Zahl, deren FN berechnet wird. Das zweite Argument ist die berechnete FN. Das letzte Argument ist die Zeit, die benötigt wird, um die Berechnung durchzuführen.

Fügen Sie immer noch in src/index.js den folgenden Code direkt unter dem vorherigen hinzu.

 const errPar = document.getElementById("error"); const btn = document.getElementById("submit-btn"); const input = document.getElementById("number-input"); const resultsContainer = document.getElementById("results-container"); btn.addEventListener("click", (e) => { errPar.textContent = ''; const num = window.Number(input.value); if (num < 2) { errPar.textContent = "Please enter a number greater than 2"; return; } const startTime = new Date().getTime(); const sum = fib(num); const time = new Date().getTime() - startTime; const resultDiv = document.createElement("div"); resultDiv.innerHTML = textCont(num, sum, time); resultDiv.className = "result-div"; resultsContainer.appendChild(resultDiv); });

Zuerst verwenden wir die document -API, um DOM -Knoten in unserer HTML-Datei zu erhalten. Wir erhalten einen Verweis auf den Absatz, in dem wir Fehlermeldungen anzeigen; die Eingabe; die Berechnungsschaltfläche und der Container, in dem wir unsere Ergebnisse anzeigen.

Als Nächstes hängen wir einen „click“-Event-Handler an die Schaltfläche an. Wenn auf die Schaltfläche geklickt wird, nehmen wir alles, was sich im Eingabeelement befindet, und wandeln es in eine Zahl um. Wenn wir weniger als 2 erhalten, zeigen wir eine Fehlermeldung an und kehren zurück. Wenn wir eine Zahl größer als 2 erhalten, fahren wir fort. Zuerst erfassen wir die aktuelle Uhrzeit. Danach berechnen wir die FN. Wenn dies abgeschlossen ist, erhalten wir eine Zeitdifferenz, die angibt, wie lange die Berechnung gedauert hat. Im verbleibenden Teil des Codes erstellen wir ein neues div . Dann legen wir seinen inneren HTML-Code so fest, dass er die Ausgabe der Funktion textCont() ist, die wir zuvor definiert haben. Schließlich fügen wir eine Klasse hinzu (für das Styling) und hängen sie an den Ergebniscontainer an. Der Effekt davon ist, dass jede Berechnung in einem separaten div unter der vorherigen erscheint.

Berechnete Fibonacci-Zahlen bis 43 anzeigen
Einige Fibonacci-Zahlen. (Große Vorschau)

Wir können sehen, dass mit zunehmender Zahl auch die Rechenzeit (exponentiell) zunimmt. Zum Beispiel hatten wir von 30 auf 35 einen Rechenzeitsprung von 13 ms auf 130 ms. Wir können diese Operationen immer noch als „schnell“ betrachten. Bei 40 sehen wir eine Rechenzeit von über 1 Sekunde. Auf meinem Computer stelle ich hier fest, dass die Seite nicht mehr reagiert. An diesem Punkt kann ich nicht mehr mit der Seite interagieren, während die Berechnung läuft. Ich kann mich nicht auf die Eingabe konzentrieren oder irgendetwas anderes tun.

Erinnern Sie sich, als wir darüber gesprochen haben, dass JavaScript Single-Threaded ist? Nun, dieser Thread wurde durch diese lang andauernde Berechnung „blockiert“, also muss alles andere „warten“, bis er fertig ist. Es kann bei einem niedrigeren oder höheren Wert auf Ihrem Computer beginnen, aber Sie werden diesen Punkt zwangsläufig erreichen. Beachten Sie, dass es fast 10 Sekunden gedauert hat, um das von 44 zu berechnen. Wenn es andere Dinge in Ihrer Web-App zu tun gäbe, nun, der Benutzer muss warten, bis Fib(44) fertig ist, bevor er fortfahren kann. Aber wenn Sie einen Web-Worker einsetzen, der diese Berechnung durchführt, könnten Ihre Benutzer mit etwas anderem weitermachen, während das läuft.

Sehen wir uns nun an, wie uns Webworker bei der Lösung dieses Problems helfen.

Ein Beispiel für einen Web Worker in Aktion

In diesem Abschnitt delegieren wir die Aufgabe, den n-ten FN zu berechnen, an einen Webworker. Dies wird dazu beitragen, den Hauptthread freizugeben und unsere Benutzeroberfläche reaktionsfähig zu halten, während die Berechnung läuft.

Die ersten Schritte mit Webworkern sind überraschend einfach. Mal sehen wie. Erstellen Sie eine neue Datei src/fib-worker.js . und geben Sie den folgenden Code ein.

 const fib = (n) => (n < 2 ? n : fib(n - 1) + fib(n - 2)); onmessage = (e) => { const { num } = e.data; const startTime = new Date().getTime(); const fibNum = fib(num); postMessage({ fibNum, time: new Date().getTime() - startTime, }); };

Beachten Sie, dass wir die Funktion, die die n-te Fibonacci-Zahl berechnet, fib in diese Datei verschoben haben. Diese Datei wird von unserem Webworker ausgeführt.

Erinnern Sie sich, dass wir im Abschnitt Was ist ein Web-Worker erwähnt haben, dass Web-Worker und ihre Eltern mithilfe des onmessage -Event-Handlers und der postMessage() Methode kommunizieren. Hier verwenden wir den Ereignishandler onmessage , um Nachrichten vom übergeordneten Skript abzuhören. Sobald wir eine Nachricht erhalten, destrukturieren wir die Nummer aus dem Datenattribut des Ereignisses. Als nächstes erhalten wir die aktuelle Uhrzeit und starten die Berechnung. Sobald das Ergebnis fertig ist, verwenden wir die Methode postMessage() , um die Ergebnisse zurück an das übergeordnete Skript zu senden.

Öffnen Sie src/index.js und nehmen Sie einige Änderungen vor.

 ... const worker = new window.Worker("src/fib-worker.js"); btn.addEventListener("click", (e) => { errPar.textContent = ""; const num = window.Number(input.value); if (num < 2) { errPar.textContent = "Please enter a number greater than 2"; return; } worker.postMessage({ num }); worker.onerror = (err) => err; worker.onmessage = (e) => { const { time, fibNum } = e.data; const resultDiv = document.createElement("div"); resultDiv.innerHTML = textCont(num, fibNum, time); resultDiv.className = "result-div"; resultsContainer.appendChild(resultDiv); }; });

Als erstes müssen Sie den Web Worker mit dem Worker -Konstruktor erstellen. Dann senden wir innerhalb des Ereignis-Listeners unserer Schaltfläche eine Nummer an den Arbeiter, indem wir worker.postMessage({ num }) verwenden. Danach setzen wir eine Funktion, um auf Fehler im Worker zu lauschen. Hier geben wir einfach den Fehler zurück. Sie können sicherlich mehr tun, wenn Sie möchten, wie z. B. das Anzeigen in DOM. Als nächstes hören wir auf Nachrichten vom Arbeiter. Sobald wir eine Nachricht erhalten, destrukturieren wir time und fibNum und fahren mit dem Prozess fort, sie im DOM anzuzeigen.

Beachten Sie, dass innerhalb des Webworkers das Ereignis onmessage im Gültigkeitsbereich des Workers verfügbar ist, sodass wir es als self.onmessage und self.postMessage() hätten schreiben können. Aber im übergeordneten Skript müssen wir diese an den Worker selbst anhängen.

Im folgenden Screenshot sehen Sie die Web-Worker-Datei auf der Registerkarte „Quellen“ der Chrome-Entwicklungstools. Was Sie beachten sollten, ist, dass die Benutzeroberfläche unabhängig von der eingegebenen Nummer reaktionsfähig bleibt. Dieses Verhalten ist die Magie der Webworker.

Ansicht einer aktiven Web-Worker-Datei
Eine laufende Web-Worker-Datei. (Große Vorschau)

Wir haben mit unserer Web-App große Fortschritte gemacht. Aber es gibt noch etwas, was wir tun können, um es besser zu machen. Unsere aktuelle Implementierung verwendet einen einzelnen Worker, um jede Berechnung durchzuführen. Wenn eine neue Nachricht kommt, während eine läuft, wird die alte ersetzt. Um dies zu umgehen, können wir für jeden Anruf einen neuen Worker erstellen, um die FN zu berechnen. Sehen wir uns im nächsten Abschnitt an, wie das geht.

Arbeiten mit mehreren Web-Workern

Derzeit bearbeiten wir jede Anfrage mit einem einzigen Mitarbeiter. Somit ersetzt eine eingehende Anfrage eine vorherige, die noch nicht abgeschlossen ist. Was wir jetzt wollen, ist eine kleine Änderung vorzunehmen, um für jede Anfrage einen neuen Webworker hervorzubringen. Wir werden diesen Arbeiter töten, sobald er fertig ist.

Öffnen src/index.js und verschieben Sie die Zeile, die den Webworker erstellt, in den Click-Event-Handler der Schaltfläche. Jetzt sollte der Event-Handler wie unten aussehen.

 btn.addEventListener("click", (e) => { errPar.textContent = ""; const num = window.Number(input.value); if (num < 2) { errPar.textContent = "Please enter a number greater than 2"; return; } const worker = new window.Worker("src/fib-worker.js"); // this line has moved inside the event handler worker.postMessage({ num }); worker.onerror = (err) => err; worker.onmessage = (e) => { const { time, fibNum } = e.data; const resultDiv = document.createElement("div"); resultDiv.innerHTML = textCont(num, fibNum, time); resultDiv.className = "result-div"; resultsContainer.appendChild(resultDiv); worker.terminate() // this line terminates the worker }; });

Wir haben zwei Änderungen vorgenommen.

  1. Wir haben diese Zeile const worker = new window.Worker("src/fib-worker.js") in den Click-Event-Handler der Schaltfläche verschoben.
  2. Wir haben diese Zeile worker.terminate() hinzugefügt, um den Worker zu verwerfen, sobald wir damit fertig sind.

Für jeden Klick auf die Schaltfläche erstellen wir also einen neuen Worker, der die Berechnung durchführt. So können wir die Eingabe ständig ändern, und jedes Ergebnis erscheint auf dem Bildschirm, sobald die Berechnung abgeschlossen ist. Im Screenshot unten sehen Sie, dass die Werte für 20 und 30 vor denen von 45 erscheinen. Aber ich habe zuerst mit 45 begonnen. Sobald die Funktion für 20 und 30 zurückkehrt, wurden ihre Ergebnisse veröffentlicht und der Worker beendet. Wenn alles fertig ist, sollten wir keine Arbeiter auf der Registerkarte "Quellen" haben.

zeigt Fibonacci-Zahlen mit gekündigten Arbeitern
Abbildung mehrerer unabhängiger Arbeitnehmer. (Große Vorschau)

Wir könnten diesen Artikel genau hier beenden, aber wenn dies eine Reaktions-App wäre, wie würden wir Webworker dazu bringen? Das ist der Schwerpunkt des nächsten Abschnitts.

Web-Worker reagieren

Erstellen Sie zunächst eine neue Reaktions-App mit CRA. Kopieren Sie die Datei fib-worker.js in den Ordner public/ Ihrer React-App. Das Ablegen der Datei hier ergibt sich aus der Tatsache, dass React-Apps Single-Page-Apps sind. Das ist ungefähr das Einzige, was für die Verwendung des Workers in einer Reaktionsanwendung spezifisch ist. Alles, was hier folgt, ist pures React.

Erstellen Sie im Ordner src/ eine Datei helpers.js und exportieren Sie daraus die Funktion ordinal_suffix() .

 // src/helpers.js export const ordinal_suffix = (num) => { // 1st, 2nd, 3rd, 4th, etc. const j = num % 10; const k = num % 100; switch (true) { case j === 1 && k !== 11: return num + "st"; case j === 2 && k !== 12: return num + "nd"; case j === 3 && k !== 13: return num + "rd"; default: return num + "th"; } };

Unsere App erfordert, dass wir einen bestimmten Status beibehalten. Erstellen Sie also eine weitere Datei, src/reducer.js , und fügen Sie den Statusreduzierer ein.

 // src/reducers.js export const reducer = (state = {}, action) => { switch (action.type) { case "SET_ERROR": return { ...state, err: action.err }; case "SET_NUMBER": return { ...state, num: action.num }; case "SET_FIBO": return { ...state, computedFibs: [ ...state.computedFibs, { id: action.id, nth: action.nth, loading: action.loading }, ], }; case "UPDATE_FIBO": { const curr = state.computedFibs.filter((c) => c.id === action.id)[0]; const idx = state.computedFibs.indexOf(curr); curr.loading = false; curr.time = action.time; curr.fibNum = action.fibNum; state.computedFibs[idx] = curr; return { ...state }; } default: return state; } };

Lassen Sie uns jeden Aktionstyp nacheinander durchgehen.

  1. SET_ERROR : setzt bei Auslösung einen Fehlerzustand.
  2. SET_NUMBER : setzt den Wert in unserem Eingabefeld auf state.
  3. SET_FIBO : Fügt dem Array der berechneten FNs einen neuen Eintrag hinzu.
  4. UPDATE_FIBO : Hier suchen wir nach einem bestimmten Eintrag und ersetzen ihn durch ein neues Objekt, das die berechnete FN und die Zeit hat, die zu ihrer Berechnung benötigt wird.

Wir werden diesen Reduzierer in Kürze verwenden. Lassen Sie uns vorher die Komponente erstellen, die die berechneten FNs anzeigt. Erstellen Sie eine neue Datei src/Results.js und fügen Sie den folgenden Code ein.

 // src/Results.js import React from "react"; export const Results = (props) => { const { results } = props; return ( <div className="results-container"> {results.map((fb) => { const { id, nth, time, fibNum, loading } = fb; return ( <div key={id} className="result-div"> {loading ? ( <p> Calculating the{" "} <span className="bold"> {nth} </span>{" "} Fibonacci number... </p> ) : ( <> <p> Time: <span className="bold">{time} ms</span> </p> <p> <span className="bold"> {nth} </span>{" "} fibonnaci number:{" "} <span className="bold"> {fibNum} </span> </p> </> )} </div> ); })} </div> ); };

Mit dieser Änderung beginnen wir mit der Konvertierung unserer vorherigen index.html-Datei in jsx. Diese Datei hat eine Aufgabe: Nehmen Sie ein Array von Objekten, die berechnete FNs darstellen, und zeigen Sie sie an. Der einzige Unterschied zu dem, was wir vorher hatten, ist die Einführung eines Ladezustands . Wenn also die Berechnung läuft, zeigen wir den Ladezustand an, um den Benutzer wissen zu lassen, dass etwas passiert.

Fügen wir die letzten Teile hinzu, indem wir den Code in src/App.js . Der Code ist ziemlich lang, also machen wir es in zwei Schritten. Lassen Sie uns den ersten Codeblock hinzufügen.

 import React from "react"; import "./App.css"; import { ordinal_suffix } from "./helpers"; import { reducer } from './reducer' import { Results } from "./Results"; function App() { const [info, dispatch] = React.useReducer(reducer, { err: "", num: "", computedFibs: [], }); const runWorker = (num, id) => { dispatch({ type: "SET_ERROR", err: "" }); const worker = new window.Worker('./fib-worker.js') worker.postMessage({ num }); worker.onerror = (err) => err; worker.onmessage = (e) => { const { time, fibNum } = e.data; dispatch({ type: "UPDATE_FIBO", id, time, fibNum, }); worker.terminate(); }; }; return ( <div> <div className="heading-container"> <h1>Computing the nth Fibonnaci number</h1> </div> <div className="body-container"> <p className="error"> {info.err} </p> // ... next block of code goes here ... // <Results results={info.computedFibs} /> </div> </div> ); } export default App;

Wie gewohnt bringen wir unsere Importe ein. Dann instanziieren wir einen Status und eine Updater-Funktion mit dem useReducer-Hook. Dann definieren wir eine Funktion, runWorker() , die eine Zahl und eine ID akzeptiert und sich daran macht, einen Webworker anzurufen, um die FN für diese Zahl zu berechnen.

Beachten Sie, dass wir zum Erstellen des Workers einen relativen Pfad an den Worker-Konstruktor übergeben. Zur Laufzeit wird unser React-Code an die Datei public/index.html angehängt, sodass er die Datei fib-worker.js im selben Verzeichnis finden kann. Wenn die Berechnung abgeschlossen ist (ausgelöst durch worker.onmessage ), wird die Aktion UPDATE_FIBO ausgelöst und der Worker danach beendet. Was wir jetzt haben, ist nicht viel anders als das, was wir vorher hatten.

Im Rückgabeblock dieser Komponente rendern wir denselben HTML-Code wie zuvor. Außerdem übergeben wir das berechnete Zahlenarray zum Rendern an die <Results /> -Komponente.

Lassen Sie uns den letzten Codeblock in die return -Anweisung einfügen.

 <div className="input-div"> <input type="number" value={info.num} className="number-input" placeholder="Enter a number" onChange={(e) => dispatch({ type: "SET_NUMBER", num: window.Number(e.target.value), }) } /> <button className="btn-submit" onClick={() => { if (info.num < 2) { dispatch({ type: "SET_ERROR", err: "Please enter a number greater than 2", }); return; } const id = info.computedFibs.length; dispatch({ type: "SET_FIBO", id, loading: true, nth: ordinal_suffix(info.num), }); runWorker(info.num, id); }} > Calculate </button> </div>

Wir setzen einen onChange -Handler auf die Eingabe, um die Zustandsvariable info.num zu aktualisieren. Auf der Schaltfläche definieren wir einen onClick -Event-Handler. Wenn auf die Schaltfläche geklickt wird, prüfen wir, ob die Zahl größer als 2 ist. Beachten Sie, dass wir vor dem Aufruf runWorker() zunächst eine Aktion auslösen, um einen Eintrag zum Array der berechneten FNs hinzuzufügen. Dieser Eintrag wird aktualisiert, sobald der Arbeiter seine Arbeit beendet hat. Auf diese Weise behält jeder Eintrag seine Position in der Liste, im Gegensatz zu dem, was wir vorher hatten.

Kopieren Sie abschließend den Inhalt von styles.css von zuvor und ersetzen Sie den Inhalt von App.css .

Wir haben jetzt alles an Ort und Stelle. Starten Sie nun Ihren Reaktionsserver und spielen Sie mit einigen Zahlen herum. Beachten Sie den Ladezustand, der eine UX-Verbesserung darstellt. Beachten Sie außerdem, dass die Benutzeroberfläche auch dann reagiert, wenn Sie eine Zahl bis zu 1000 eingeben und auf „Berechnen“ klicken.

Zeigt den Ladezustand an, während der Arbeiter aktiv ist.
Ladezustand und aktiver Webworker werden angezeigt. (Große Vorschau)

Beachten Sie den Ladezustand und den aktiven Arbeiter. Sobald der 46. Wert berechnet ist, wird der Arbeiter getötet und der Ladezustand wird durch das Endergebnis ersetzt.

  • Der Quellcode für diese React-App ist auf Github verfügbar und es gibt eine gehostete App auf Vercel.

Fazit

Puh! Es war eine lange Fahrt, also lasst es uns beenden. Ich ermutige Sie, einen Blick auf den MDN-Eintrag für Webworker zu werfen (siehe Ressourcenliste unten), um andere Möglichkeiten der Verwendung von Webworkern kennenzulernen.

In diesem Artikel haben wir erfahren, was Webworker sind und welche Art von Problemen sie lösen sollen. Wir haben auch gesehen, wie man sie mit einfachem JavaScript implementiert. Schließlich haben wir gesehen, wie Webworker in einer React-Anwendung implementiert werden.

Ich ermutige Sie, diese großartige API zu nutzen, um Ihren Benutzern ein besseres Erlebnis zu bieten.

Weitere Ressourcen

  • Console.time() , MDN-Webdokumentation
  • {JSON} Platzhalter, offizielle Website
  • Mit Web Workers, MDN-Webdokumentation
  • Fibonacci-Zahl, Wikipedia
  • Bedingter (ternärer) Operator, MDN-Webdokumentation
  • Document , Web-APIs, MDN-Webdokumente
  • Erste Schritte, React-App erstellen (Dokumente)
  • Function.prototype.toString() , MDN-Webdokumentation
  • IIFE, MDN-Webdokumentation
  • workerSetup.js , Fantastische Fullstack-Tutorials, GitHub
  • „Parallele Programmierung in JavaScript mit Web Workern“, Uday Hiwarale, Medium