W poprzednim module omówiliśmy przegląd informacji na temat robotów internetowych. Roboty internetowe mogą poprawić czas reagowania na dane wejściowe, przenosząc kod JavaScript z wątku głównego do osobnych wątków, co może poprawić interakcję z instancją Next Paint (INP) witryny w pracy, która nie wymaga bezpośredniego dostępu do wątku głównego. Jednak samo omówienie nie wystarczy – w tym module przedstawimy konkretny przypadek użycia dla instancji roboczej.
Jednym z takich przypadków może być witryna, która musi usuwać z obrazu metadane Exif – nie jest to zbyt ekscytujące. Witryny takie jak Flickr oferują użytkownikom możliwość przeglądania metadanych Exif w celu poznania szczegółów technicznych przechowywanych na zdjęciach, takich jak głębia kolorów, marka i model kamery itp.
Jednak metoda pobierania obrazu, przekonwertowania go na format ArrayBuffer
i wyodrębnienia metadanych Exif może być jednak potencjalnie kosztowna, jeśli zostanie wykonana w całości w wątku głównym. Na szczęście zakres mechanizmu internetowego pozwala na wykonywanie tych zadań z poziomu głównego wątku. Następnie za pomocą potoku przesyłania wiadomości instancji internetowej metadane Exif są przesyłane z powrotem do wątku głównego jako ciąg HTML i wyświetlane użytkownikowi.
Jak wygląda wątek główny bez instancji roboczej
Po pierwsze zobacz, jak wygląda główny wątek, gdy wykonujemy tę czynność bez mechanizmu internetowego. Aby to zrobić:
- Otwórz nową kartę w Chrome i otwórz Narzędzia deweloperskie.
- Otwórz panel wydajności.
- Wejdź na stronę https://exif-worker.glitch.me/without-worker.html.
- Na panelu wydajności kliknij Record (Rejestruj) w prawym górnym rogu panelu Narzędzia deweloperskie.
- Wklej w polu ten link do zdjęcia lub inny dowolny link zawierający metadane Exif i kliknij przycisk Pobierz ten plik JPEG!.
- Gdy pojawią się w interfejsie metadane Exif, ponownie kliknij Record, aby zatrzymać rejestrowanie.
![Program do profilowania wydajności pokazujący aktywność aplikacji do wyodrębniania metadanych obrazów w całości w wątku głównym. Istnieją 2 ogólne długie zadania – pierwsze to pobieranie żądanego obrazu i jego dekodowanie, a drugie – wyodrębnianie metadanych z obrazu.](https://web.dev/static/learn/performance/web-worker-demo/image/fig-1.opt.png?authuser=7&hl=pl)
Pamiętaj, że oprócz innych wątków, które mogą występować, takich jak wątki rastrowania, wszystko, co dzieje się w aplikacji, odbywa się w wątku głównym. W wątku głównym:
- Formularz pobiera dane wejściowe i wysyła żądanie
fetch
, aby uzyskać początkową część obrazu zawierającego metadane Exif. - Dane obrazu są konwertowane na format
ArrayBuffer
. - Skrypt
exif-reader
służy do wyodrębniania metadanych Exif z obrazu. - Metadane są pobierane w celu utworzenia ciągu HTML, który następnie wypełnia przeglądarkę metadanych.
Porównajmy teraz ten sam mechanizm z implementacją tego samego zachowania, ale z użyciem robota internetowego.
Jak wygląda wątek główny z robotem internetowym
Skoro wiesz już, jak wygląda wyodrębnianie metadanych Exif z pliku JPEG w wątku głównym, zobacz, jak to wygląda w przypadku robota internetowego:
- Otwórz inną kartę w Chrome, a następnie wybierz Narzędzia deweloperskie.
- Otwórz panel wydajności.
- Wejdź na stronę https://exif-worker.glitch.me/with-worker.html.
- Na panelu wydajności kliknij przycisk rejestrowania w prawym górnym rogu panelu Narzędzia deweloperskie.
- Wklej w polu ten link do zdjęcia i kliknij przycisk Pobierz ten plik JPEG!.
- Gdy pojawią się w interfejsie metadane Exif, ponownie kliknij przycisk nagrywania, aby zatrzymać rejestrowanie.
![Program do profilowania wydajności pokazujący aktywność aplikacji do wyodrębniania metadanych obrazów występującą zarówno w wątku głównym, jak i w wątku instancji roboczej. Chociaż w wątku głównym nadal są wykonywane długie zadania, są one znacznie krótsze, a pobieranie/dekodowanie obrazu i wyodrębnianie metadanych odbywa się w całości w wątku mechanizmu internetowego. Jedyna praca w wątku głównym wymaga przekazywania danych do i z instancji roboczej.](https://web.dev/static/learn/performance/web-worker-demo/image/fig-2.opt.png?authuser=7&hl=pl)
To jest potęga narzędzi internetowych. Zamiast wykonywać wszystkie czynności w wątku głównym, wszystko poza zapełnianiem przeglądarki metadanych kodem HTML odbywa się w osobnym wątku. Oznacza to, że wątek główny może być wykonywany na inne zadania.
Prawdopodobnie największą zaletą jest to, że w przeciwieństwie do tej aplikacji, która nie korzysta z mechanizmów roboczych, skrypt exif-reader
nie jest wczytywany w wątku głównym, tylko w wątku tego procesu. Oznacza to, że koszty pobierania, analizowania i kompilowania skryptu exif-reader
są pobierane poza wątek główny.
Przejdźmy teraz do kodu robota internetowego, dzięki któremu to wszystko jest możliwe.
Rzut oka na kod instancji roboczej
Nie wystarczy dostrzec różnicy w stosunku do zasobów internetowych, ale warto też zrozumieć – przynajmniej w tym przypadku – jak wygląda kod, by sprawdzić, co jest możliwe w zakresie instancji internetowych.
Zacznij od kodu wątku głównego, który musi wystąpić, zanim robot internetowy będzie mógł zobaczyć obraz:
// scripts.js
// Register the Exif reader web worker:
const exifWorker = new Worker('/js/with-worker/exif-worker.js');
// We have to send image requests through this proxy due to CORS limitations:
const imageFetchPrefix = 'https://res.cloudinary.com/demo/image/fetch/';
// Necessary elements we need to select:
const imageFetchPanel = document.getElementById('image-fetch');
const imageExifDataPanel = document.getElementById('image-exif-data');
const exifDataPanel = document.getElementById('exif-data');
const imageInput = document.getElementById('image-url');
// What to do when the form is submitted.
document.getElementById('image-form').addEventListener('submit', event => {
// Don't let the form submit by default:
event.preventDefault();
// Send the image URL to the web worker on submit:
exifWorker.postMessage(`${imageFetchPrefix}${imageInput.value}`);
});
// This listens for the Exif metadata to come back from the web worker:
exifWorker.addEventListener('message', ({ data }) => {
// This populates the Exif metadata viewer:
exifDataPanel.innerHTML = data.message;
imageFetchPanel.style.display = 'none';
imageExifDataPanel.style.display = 'block';
});
Ten kod jest uruchamiany w wątku głównym i konfiguruje formularz do wysyłania adresu URL obrazu do instancji roboczej. Następnie kod instancji roboczej zaczyna się od instrukcji importScripts
, która wczytuje zewnętrzny skrypt exif-reader
, a następnie konfiguruje potok komunikacji w wątku głównym:
// exif-worker.js
// Import the exif-reader script:
importScripts('/js/with-worker/exifreader.js');
// Set up a messaging pipeline to send the Exif data to the `window`:
self.addEventListener('message', ({ data }) => {
getExifDataFromImage(data).then(status => {
self.postMessage(status);
});
});
Ten fragment JavaScriptu konfiguruje potok przesyłania wiadomości, dzięki czemu gdy użytkownik przesyła formularz z adresem URL prowadzącym do pliku JPEG, adres URL dociera do instancji roboczej.
Następny fragment kodu wyodrębnia metadane Exif z pliku JPEG, tworzy ciąg znaków HTML i wysyła go z powrotem do window
, aby został wyświetlony użytkownikowi:
// Takes a blob to transform the image data into an `ArrayBuffer`:
// NOTE: these promises are simplified for readability, and don't include
// rejections on failures. Check out the complete web worker code:
// https://glitch.com/edit/#!/exif-worker?path=js%2Fwith-worker%2Fexif-worker.js%3A10%3A5
const readBlobAsArrayBuffer = blob => new Promise(resolve => {
const reader = new FileReader();
reader.onload = () => {
resolve(reader.result);
};
reader.readAsArrayBuffer(blob);
});
// Takes the Exif metadata and converts it to a markup string to
// display in the Exif metadata viewer in the DOM:
const exifToMarkup = exif => Object.entries(exif).map(([exifNode, exifData]) => {
return `
<details>
<summary>
<h2>${exifNode}</h2>
</summary>
<p>${exifNode === 'base64' ? `<img src="data:image/jpeg;base64,${exifData}">` : typeof exifData.value === 'undefined' ? exifData : exifData.description || exifData.value}</p>
</details>
`;
}).join('');
// Fetches a partial image and gets its Exif data
const getExifDataFromImage = imageUrl => new Promise(resolve => {
fetch(imageUrl, {
headers: {
// Use a range request to only download the first 64 KiB of an image.
// This ensures bandwidth isn't wasted by downloading what may be a huge
// JPEG file when all that's needed is the metadata.
'Range': `bytes=0-${2 ** 10 * 64}`
}
}).then(response => {
if (response.ok) {
return response.clone().blob();
}
}).then(responseBlob => {
readBlobAsArrayBuffer(responseBlob).then(arrayBuffer => {
const tags = ExifReader.load(arrayBuffer, {
expanded: true
});
resolve({
status: true,
message: Object.values(tags).map(tag => exifToMarkup(tag)).join('')
});
});
});
});
Jest trochę nieczytelny, ale ten przypadek użycia jest też dość stosowany w przypadku osób pracujących z internetem.
Efekty są jednak warte nakładu pracy i nie ograniczają się jedynie do tego przypadku użycia.
Narzędzi internetowych możesz używać do różnego rodzaju zadań, takich jak izolowanie wywołań fetch
i przetwarzanie odpowiedzi, przetwarzanie dużych ilości danych bez blokowania głównego wątku – a to tylko na początek.
Poprawiając wydajność swoich aplikacji internetowych, zacznij myśleć o tym, co da się zrobić w kontekście procesów internetowych. Zyski mogą być znaczne i mogą poprawić ogólną wygodę korzystania z Twojej witryny.