Przypadek użycia konkretnego narzędzia do pracy w sieci

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ć:

  1. Otwórz nową kartę w Chrome i otwórz Narzędzia deweloperskie.
  2. Otwórz panel wydajności.
  3. Wejdź na stronę https://exif-worker.glitch.me/without-worker.html.
  4. Na panelu wydajności kliknij Record (Rejestruj) w prawym górnym rogu panelu Narzędzia deweloperskie.
  5. Wklej w polu ten link do zdjęcia lub inny dowolny link zawierający metadane Exif i kliknij przycisk Pobierz ten plik JPEG!.
  6. 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.
Aktywność w głównym wątku w aplikacji do wyodrębniania metadanych obrazów. Cała aktywność ma miejsce w wątku głównym.

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:

  1. Formularz pobiera dane wejściowe i wysyła żądanie fetch, aby uzyskać początkową część obrazu zawierającego metadane Exif.
  2. Dane obrazu są konwertowane na format ArrayBuffer.
  3. Skrypt exif-reader służy do wyodrębniania metadanych Exif z obrazu.
  4. 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:

  1. Otwórz inną kartę w Chrome, a następnie wybierz Narzędzia deweloperskie.
  2. Otwórz panel wydajności.
  3. Wejdź na stronę https://exif-worker.glitch.me/with-worker.html.
  4. Na panelu wydajności kliknij przycisk rejestrowania w prawym górnym rogu panelu Narzędzia deweloperskie.
  5. Wklej w polu ten link do zdjęcia i kliknij przycisk Pobierz ten plik JPEG!.
  6. 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.
Aktywność w głównym wątku w aplikacji do wyodrębniania metadanych obrazów. Zwróć uwagę, że istnieje dodatkowy wątek instancji roboczej, w ramach którego wykonuje się większość pracy.

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.