Zarządzanie długotrwałymi zadaniami w aplikacji React za pomocą pracowników internetowych
Opublikowany: 2022-03-10Czas odpowiedzi to duża sprawa, jeśli chodzi o aplikacje internetowe. Użytkownicy wymagają natychmiastowych odpowiedzi, bez względu na to, co robi Twoja aplikacja. Niezależnie od tego, czy chodzi tylko o wyświetlanie imienia i nazwiska danej osoby, czy o przeszukiwanie numerów, użytkownicy aplikacji internetowych wymagają, aby Twoja aplikacja za każdym razem odpowiadała na ich polecenia. Czasami może to być trudne do osiągnięcia, biorąc pod uwagę jednowątkowy charakter JavaScript. Jednak w tym artykule dowiemy się, w jaki sposób możemy wykorzystać interfejs Web Worker API, aby zapewnić lepszą obsługę.
Pisząc ten artykuł, przyjąłem następujące założenia:
- Aby móc podążać dalej, powinieneś przynajmniej trochę zaznajomić się z JavaScript i dokumentem API;
- Powinieneś również posiadać praktyczną wiedzę na temat React, aby z powodzeniem rozpocząć nowy projekt React za pomocą aplikacji Create React.
Jeśli potrzebujesz więcej informacji na ten temat, w sekcji „Dalsze zasoby” zamieściłem kilka linków, które pomogą Ci szybko zapoznać się z tematem.
Zacznijmy od Web Workers.
Co to jest pracownik sieciowy?
Aby zrozumieć Web Workers i problem, który mają rozwiązać, konieczne jest zrozumienie, w jaki sposób kod JavaScript jest wykonywany w czasie wykonywania. W czasie wykonywania kod JavaScript jest wykonywany sekwencyjnie i krok po kroku. Gdy kończy się fragment kodu, zaczyna działać następny i tak dalej. W kategoriach technicznych mówimy, że JavaScript jest jednowątkowy. To zachowanie oznacza, że gdy jakiś fragment kodu zacznie działać, każdy następny kod musi poczekać na zakończenie wykonywania tego kodu. W ten sposób każdy wiersz kodu „blokuje” wykonanie wszystkiego, co następuje po nim. Dlatego pożądane jest, aby każdy fragment kodu został ukończony tak szybko, jak to możliwe. Jeśli wykonanie jakiegoś fragmentu kodu trwa zbyt długo, wydaje się, że nasz program przestał działać. W przeglądarce objawia się to jako zawieszona, niereagująca strona. W niektórych skrajnych przypadkach karta całkowicie się zawiesi.
Wyobraź sobie jazdę po jednym pasie. Jeśli któryś z kierowców przed Tobą z jakiegoś powodu przestanie się poruszać, oznacza to, że masz korek. Z programem takim jak Java ruch mógłby być kontynuowany na innych pasach. Dlatego mówi się, że Java jest wielowątkowa. Web Workery to próba wprowadzenia wielowątkowego zachowania do JavaScriptu.
Poniższy zrzut ekranu pokazuje, że interfejs Web Worker API jest obsługiwany przez wiele przeglądarek, więc powinieneś czuć się pewnie, używając go.
Web Workery działają w wątkach w tle bez ingerencji w interfejs użytkownika i komunikują się z kodem, który je utworzył, za pomocą programów obsługi zdarzeń.
Doskonała definicja Web Workera pochodzi z MDN:
„Pracownik to obiekt utworzony za pomocą konstruktora (np.Worker()
, który uruchamia nazwany plik JavaScript — ten plik zawiera kod, który będzie działał w wątku roboczym; pracownicy działają w innym kontekście globalnym, innym niż bieżącewindow
. , używając skrótu dowindow
, aby uzyskać bieżący zasięg globalny (zamiastself
w ramachWorker
zwróci błąd.”
Pracownik jest tworzony za pomocą konstruktora Worker
.
const worker = new Worker('worker-file.js')
Możliwe jest uruchomienie większości kodu wewnątrz webworkera, z pewnymi wyjątkami. Na przykład nie możesz manipulować DOM od wewnątrz pracownika. Brak dostępu do interfejsu API document
.
Pracownicy i wątek, który je tworzy, wysyłają do siebie wiadomości za pomocą metody postMessage()
. Podobnie odpowiadają na komunikaty za pomocą obsługi zdarzeń onmessage
. Ważne jest, aby uzyskać tę różnicę. Wysyłanie wiadomości odbywa się za pomocą metody; odbieranie wiadomości zwrotnej wymaga obsługi zdarzeń. Odebrany komunikat jest zawarty w atrybucie data
zdarzenia. Zobaczymy tego przykład w następnej sekcji. Pozwolę sobie jednak szybko wspomnieć, że rodzaj pracownika, o którym rozmawialiśmy, nazywa się „pracownikiem oddanym”. Oznacza to, że pracownik jest dostępny tylko dla skryptu, który go wywołał. Możliwe jest również posiadanie pracownika, do którego można uzyskać dostęp z wielu skryptów. Są one nazywane współdzielonymi pracownikami i są tworzone przy użyciu konstruktora SharedWorker
, jak pokazano poniżej.
const sWorker = new SharedWorker('shared-worker-file.js')
Aby dowiedzieć się więcej o pracownikach, zapoznaj się z tym artykułem MDN. Celem tego artykułu jest rozpoczęcie korzystania z pracowników sieci Web. Przejdźmy do tego, obliczając n-tą liczbę Fibonacciego.
Obliczanie N-tej liczby Fibonacciego
Uwaga: W tej i następnych dwóch sekcjach używam Live Server na VSCode do uruchamiania aplikacji. Z pewnością możesz użyć czegoś innego.
To jest sekcja, na którą czekałeś. W końcu napiszemy trochę kodu, aby zobaczyć Web Workers w akcji. Cóż, nie tak szybko. Nie docenilibyśmy pracy, jaką wykonuje Web Worker, jeśli nie napotkamy problemów, które on rozwiązuje. W tej sekcji zobaczymy przykładowy problem, aw kolejnej zobaczymy, jak pracownik sieciowy pomaga nam działać lepiej.
Wyobraź sobie, że tworzysz aplikację internetową, która pozwala użytkownikom obliczyć n-tą liczbę Fibonacciego. Jeśli nie znasz terminu „liczba Fibonacciego”, możesz przeczytać o nim więcej tutaj, ale w skrócie, liczby Fibonacciego są sekwencją liczb, tak że każda liczba jest sumą dwóch poprzednich liczb.
Matematycznie wyraża się to jako:
Tak więc kilka pierwszych liczb ciągu to:
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 ...
W niektórych źródłach sekwencja zaczyna się od F 0 = 0
, w którym to przypadku poniższy wzór obowiązuje dla n > 1
:
W tym artykule zaczniemy od F 1 = 1. Jedną z rzeczy, które widzimy od razu z formuły, jest to, że liczby mają wzór rekurencyjny. Teraz zadaniem jest napisanie funkcji rekurencyjnej do obliczenia n-tej liczby Fibonacciego (FN).
Uważam, że po kilku próbach możesz łatwo wymyślić poniższą funkcję.
const fib = n => { if (n < 2) { return n // or 1 } else { return fib(n - 1) + fib(n - 2) } }
Funkcja jest prosta. Jeśli n jest mniejsze niż 2, zwróć n (lub 1), w przeciwnym razie zwróć sumę n-1
i n-2
FN. Dzięki funkcjom strzałkowym i operatorowi trójskładnikowemu możemy wymyślić jednowierszowy.
const fib = n => (n < 2 ? n : fib(n-1) + fib(n-2))
Ta funkcja ma złożoność czasową 0(2 n )
. Oznacza to po prostu, że wraz ze wzrostem wartości n czas wymagany do obliczenia sumy rośnie wykładniczo. To sprawia, że jest to naprawdę długotrwałe zadanie, które może potencjalnie zakłócać nasz interfejs użytkownika, przy dużych wartościach n. Zobaczmy to w akcji.
Uwaga : nie jest to w żadnym wypadku najlepszy sposób rozwiązania tego konkretnego problemu. Mój wybór użycia tej metody jest na potrzeby tego artykułu.
Aby rozpocząć, utwórz nowy folder i nazwij go, jak chcesz. Teraz wewnątrz tego folderu utwórz folder src/
. Ponadto utwórz plik index.html
w folderze głównym. Wewnątrz folderu src/
utwórz plik o nazwie index.js
.
Otwórz index.html
i dodaj następujący kod HTML.
<!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>
Ta część jest bardzo prosta. Po pierwsze, mamy nagłówek. Następnie mamy kontener z wejściem i przyciskiem. Użytkownik wprowadzi liczbę, a następnie kliknie „Oblicz”. Posiadamy również pojemnik do przechowywania wyniku obliczeń. Na koniec dołączamy plik src/index.js
do tagu script
.
Możesz usunąć link do arkusza stylów. Ale jeśli masz mało czasu, zdefiniowałem kilka CSS, których możesz użyć. Po prostu utwórz plik styles.css
w folderze głównym i dodaj poniższe style:
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; }
Teraz otwórz src/index.js
, powoli rozwijajmy go. Dodaj kod poniżej.
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> `; };
Tutaj mamy trzy funkcje. Pierwsza z nich to funkcja, którą widzieliśmy wcześniej do obliczania n-tego FN. Druga funkcja jest po prostu funkcją użytkową, która dołącza odpowiedni sufiks do liczby całkowitej. Trzecia funkcja pobiera kilka argumentów i wyświetla znacznik, który później wstawimy do DOM. Pierwszym argumentem jest liczba, której FN jest obliczane. Drugim argumentem jest obliczony FN. Ostatni argument to czas potrzebny na wykonanie obliczenia.
Nadal w src/index.js
dodaj poniższy kod tuż pod poprzednim.
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); });
Najpierw używamy interfejsu API document
, aby uzyskać węzły DOM
w naszym pliku HTML. Otrzymujemy odniesienie do akapitu, w którym będziemy wyświetlać komunikaty o błędach; wejście; przycisk oblicz i kontener, w którym pokażemy nasze wyniki.
Następnie dołączamy do przycisku obsługę zdarzenia „klik”. Kiedy przycisk zostanie kliknięty, bierzemy to, co znajduje się w elemencie wejściowym i konwertujemy to na liczbę, jeśli otrzymamy cokolwiek poniżej 2, wyświetlamy komunikat o błędzie i wracamy. Jeśli otrzymamy liczbę większą niż 2, kontynuujemy. Najpierw rejestrujemy aktualny czas. Następnie obliczamy FN. Kiedy to się skończy, otrzymujemy różnicę czasu, która reprezentuje czas trwania obliczeń. W pozostałej części kodu tworzymy nowy div
. Następnie ustawiamy jego wewnętrzny kod HTML na wyjście funkcji textCont()
, którą zdefiniowaliśmy wcześniej. Na koniec dodajemy do niego klasę (do stylizacji) i dołączamy ją do kontenera wyników. Efektem tego jest to, że każde obliczenie pojawi się w osobnym div
poniżej poprzedniego.
Widzimy, że wraz ze wzrostem liczby rośnie również czas obliczeń (wykładniczy). Na przykład, z 30 do 35, mieliśmy skok czasu obliczeń z 13 ms do 130 ms. Nadal możemy uznać te operacje za „szybkie”. W 40 widzimy czas obliczeń powyżej 1 sekundy. Na moim komputerze zaczynam zauważać, że strona przestaje odpowiadać. W tym momencie nie mogę już wchodzić w interakcję ze stroną, gdy trwają obliczenia. Nie mogę skoncentrować się na wejściu ani zrobić nic innego.
Przypomnij sobie, kiedy rozmawialiśmy o jednowątkowym JavaScript? Cóż, ten wątek został „zablokowany” przez te długotrwałe obliczenia, więc wszystko inne musi „poczekać”, aż się zakończy. Może zacząć się od niższej lub wyższej wartości na twoim komputerze, ale na pewno osiągniesz ten punkt. Zauważ, że obliczenie liczby 44 zajęło prawie 10 sekund. Jeśli były inne rzeczy do zrobienia w Twojej aplikacji internetowej, cóż, użytkownik musi poczekać na zakończenie Fib(44), zanim będzie mógł kontynuować. Ale jeśli wdrożysz pracownika sieci Web do obsługi tych obliczeń, Twoi użytkownicy mogą w trakcie działania wykonywać coś innego.
Zobaczmy teraz, jak pracownicy sieciowi pomagają nam rozwiązać ten problem.
Przykładowy pracownik sieciowy w akcji
W tej sekcji przekażemy zadanie obliczenia n-tego FN pracownikowi WWW. Pomoże to uwolnić główny wątek i utrzymać responsywność naszego interfejsu użytkownika podczas trwania obliczeń.
Rozpoczęcie pracy z pracownikami sieciowymi jest zaskakująco proste. Zobaczmy jak. Utwórz nowy plik src/fib-worker.js
. i wprowadź następujący kod.
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, }); };
Zauważ, że przenieśliśmy funkcję obliczającą n-tą liczbę Fibonacciego, fib
wewnątrz tego pliku. Ten plik będzie uruchamiany przez naszego webworkera.
Przypomnijmy w sekcji Czym jest pracownik sieciowy , wspomnieliśmy, że pracownicy sieciowi i ich rodzic komunikują się za pomocą procedury obsługi zdarzeń onmessage
i metody postMessage()
. Tutaj używamy obsługi zdarzeń onmessage
do nasłuchiwania komunikatów ze skryptu nadrzędnego. Gdy otrzymamy wiadomość, destrukturyzujemy liczbę z atrybutu data zdarzenia. Następnie otrzymujemy aktualny czas i rozpoczynamy obliczenia. Gdy wynik jest gotowy, używamy metody postMessage()
, aby wysłać wyniki z powrotem do skryptu nadrzędnego.
Otwórz src/index.js
, zróbmy kilka zmian.
... 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); }; });
Pierwszą rzeczą do zrobienia jest utworzenie pracownika sieci Web za pomocą konstruktora Worker
. Następnie w detektorze zdarzeń naszego przycisku wysyłamy numer do pracownika za pomocą worker.postMessage({ num })
. Następnie ustawiamy funkcję nasłuchiwania błędów w pracowniku. Tutaj po prostu zwracamy błąd. Z pewnością możesz zrobić więcej, jeśli chcesz, na przykład pokazać to w DOM. Następnie nasłuchujemy wiadomości od pracownika. Gdy otrzymamy wiadomość, destrukturyzujemy time
i fibNum
i kontynuujemy proces pokazywania ich w DOM.
Zwróć uwagę, że wewnątrz pracownika sieciowego zdarzenie onmessage
jest dostępne w jego zakresie, więc mogliśmy napisać je jako self.onmessage
i self.postMessage()
. Ale w skrypcie nadrzędnym musimy dołączyć je do samego pracownika.
Na poniższym zrzucie ekranu zobaczysz plik Web Worker na karcie źródeł w Chrome Dev Tools. Powinieneś zauważyć, że interfejs użytkownika pozostaje responsywny bez względu na wprowadzony numer. Takie zachowanie to magia pracowników sieci.
Dzięki naszej aplikacji internetowej osiągnęliśmy duże postępy. Ale jest coś jeszcze, co możemy zrobić, aby to ulepszyć. Nasza obecna implementacja wykorzystuje jednego pracownika do obsługi każdego obliczenia. Jeśli nowa wiadomość pojawi się, gdy jest uruchomiona, stara zostanie zastąpiona. Aby obejść ten problem, możemy utworzyć nowego pracownika dla każdego połączenia, aby obliczyć FN. Zobaczmy, jak to zrobić w następnej sekcji.
Praca z wieloma pracownikami sieciowymi
Obecnie każde zgłoszenie obsługujemy z jednym pracownikiem. W ten sposób przychodzące żądanie zastąpi poprzednie, które jeszcze się nie zakończyło. Teraz chcemy wprowadzić małą zmianę, aby dla każdego żądania utworzyć nowego pracownika sieci Web. Zabijemy tego robotnika, gdy to się skończy.
Otwórz src/index.js
i przenieś wiersz, który tworzy pracownika sieci Web, do modułu obsługi zdarzeń kliknięcia przycisku. Teraz obsługa zdarzeń powinna wyglądać jak poniżej.
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 }; });
Wprowadziliśmy dwie zmiany.
- Przenieśliśmy ten wiersz
const worker = new window.Worker("src/fib-worker.js")
do modułu obsługi zdarzenia kliknięcia przycisku. - Dodaliśmy tę linię
worker.terminate()
, aby odrzucić pracownika, gdy już z nim skończymy.
Tak więc za każdym kliknięciem przycisku tworzymy nowego pracownika do obsługi obliczeń. W ten sposób możemy zmieniać dane wejściowe, a każdy wynik pojawi się na ekranie po zakończeniu obliczeń. Na poniższym zrzucie ekranu widać, że wartości dla 20 i 30 pojawiają się przed wartością 45. Ale najpierw zacząłem 45. Gdy funkcja zwraca wartości 20 i 30, ich wyniki zostały opublikowane, a pracownik został przerwany. Kiedy wszystko się skończy, nie powinniśmy mieć żadnych pracowników w zakładce źródeł.
Moglibyśmy zakończyć ten artykuł w tym miejscu, ale gdyby była to aplikacja React, jak moglibyśmy wprowadzić do niej pracowników internetowych. Na tym skupia się następna sekcja.
Web Workers w React
Aby rozpocząć, utwórz nową aplikację React za pomocą CRA. Skopiuj plik fib-worker.js
do folderu public/
swojej aplikacji React. Umieszczenie pliku tutaj wynika z faktu, że aplikacje React są aplikacjami jednostronicowymi. To jedyna rzecz, która jest charakterystyczna dla wykorzystania pracownika w aplikacji React. Wszystko, co stąd wynika, to czysty React.
W folderze src/
utwórz plik helpers.js
i wyeksportuj z niego funkcję 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"; } };
Nasza aplikacja będzie wymagała od nas utrzymania pewnego stanu, więc utwórz kolejny plik, src/reducer.js
i wklej reduktor stanu.
// 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; } };
Przyjrzyjmy się po kolei każdemu rodzajowi akcji.
-
SET_ERROR
: ustawia stan błędu po uruchomieniu. -
SET_NUMBER
: ustawia wartość w naszym polu wprowadzania na stan. -
SET_FIBO
: dodaje nowy wpis do tablicy obliczonych FN. -
UPDATE_FIBO
: tutaj szukamy konkretnego wpisu i zastępujemy go nowym obiektem, który ma wyliczony FN i czas potrzebny na jego obliczenie.
Niedługo użyjemy tego reduktora. Wcześniej utwórzmy komponent, który będzie wyświetlał obliczone FN. Utwórz nowy plik src/Results.js
i wklej poniższy kod.
// 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> ); };
Wraz z tą zmianą rozpoczynamy proces konwersji naszego poprzedniego pliku index.html do formatu jsx. Ten plik ma jedną odpowiedzialność: weź tablicę obiektów reprezentujących obliczone FN i wyświetl je. Jedyną różnicą w stosunku do tego, co mieliśmy wcześniej, jest wprowadzenie stanu ładowania . Więc teraz, gdy obliczenia są uruchomione, pokazujemy stan ładowania, aby użytkownik wiedział, że coś się dzieje.
Wstawmy ostatnie elementy, aktualizując kod w src/App.js
. Kod jest dość długi, więc zrobimy to w dwóch krokach. Dodajmy pierwszy blok kodu.
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;
Jak zwykle sprowadzamy nasz import. Następnie tworzymy instancję stanu i funkcji aktualizującej z hakiem useReducer. Następnie definiujemy funkcję runWorker()
, która pobiera liczbę oraz identyfikator i wywołuje pracownika sieci Web, aby obliczyć FN dla tego numeru.
Zauważ, że aby utworzyć pracownika, przekazujemy ścieżkę względną do konstruktora pracownika. W czasie wykonywania nasz kod React zostaje dołączony do pliku public/index.html
, dzięki czemu może znaleźć plik fib-worker.js
w tym samym katalogu. Po zakończeniu obliczeń (wyzwolonych przez worker.onmessage
), akcja UPDATE_FIBO
zostaje wysłana, a pracownik zostaje następnie zakończony. To, co mamy teraz, niewiele różni się od tego, co mieliśmy wcześniej.
W bloku return tego komponentu renderujemy ten sam kod HTML, który mieliśmy wcześniej. Przekazujemy również tablicę obliczonych liczb do komponentu <Results />
w celu renderowania.
Dodajmy ostatni blok kodu wewnątrz instrukcji return
.
<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>
Ustawiamy procedurę obsługi onChange
na wejściu, aby zaktualizować zmienną stanu info.num
. Na przycisku definiujemy obsługę zdarzeń onClick
. Po kliknięciu przycisku sprawdzamy, czy liczba jest większa niż 2. Zauważ, że przed wywołaniem runWorker()
najpierw wywołujemy akcję dodania wpisu do tablicy obliczonych FN. To właśnie ten wpis zostanie zaktualizowany po zakończeniu pracy przez pracownika. W ten sposób każdy wpis zachowuje swoją pozycję na liście, w przeciwieństwie do tego, co mieliśmy wcześniej.
Na koniec skopiuj zawartość styles.css
z poprzedniego i zastąp zawartość App.css
.
Teraz mamy wszystko na swoim miejscu. Teraz uruchom serwer React i pobaw się kilkoma liczbami. Zwróć uwagę na stan ładowania, który jest ulepszeniem UX. Pamiętaj też, że interfejs użytkownika pozostaje responsywny, nawet jeśli wpiszesz liczbę nawet 1000 i klikniesz „Oblicz”.
Zanotuj stan ładowania i aktywnego pracownika. Po obliczeniu 46. wartości pracownik zostaje zabity, a stan obciążenia zostaje zastąpiony wynikiem końcowym.
- Kod źródłowy tej aplikacji React jest dostępny na Github, a aplikacja hostowana na vercel.
Wniosek
Uff! To była długa jazda, więc podsumujmy. Zachęcam do zapoznania się z wpisem MDN dla pracowników sieci Web (patrz lista zasobów poniżej), aby poznać inne sposoby korzystania z pracowników sieci Web.
W tym artykule dowiedzieliśmy się, czym są pracownicy sieciowi i jakie problemy mają rozwiązać. Zobaczyliśmy również, jak je zaimplementować za pomocą zwykłego JavaScript. Na koniec zobaczyliśmy, jak zaimplementować webworkery w aplikacji React.
Zachęcam do skorzystania z tego wspaniałego interfejsu API, aby zapewnić użytkownikom lepsze wrażenia.
Dalsze zasoby
-
Console.time()
, dokumentacja internetowa MDN - {JSON} Symbol zastępczy, oficjalna strona internetowa
- Korzystanie z Web Workers, dokumentów internetowych MDN
- Liczba Fibonacciego, Wikipedia
- Operator warunkowy (trójargumentowy), dokumentacja internetowa MDN
-
Document
, Web API, MDN Web Docs - Pierwsze kroki, utwórz aplikację React (dokumenty)
-
Function.prototype.toString()
, dokumentacja internetowa MDN - Dokumentacja internetowa IIFE, MDN
-
workerSetup.js
, Niesamowite samouczki Fullstack, GitHub - „Programowanie równoległe w JavaScript przy użyciu robotów internetowych”, Uday Hiwarale, Medium