Kasus penggunaan pekerja web konkret

Pada modul terakhir, ringkasan web worker telah diberikan. Web worker dapat meningkatkan responsivitas input dengan memindahkan JavaScript dari thread utama ke thread web worker terpisah, yang dapat membantu meningkatkan Interaksi ke Paint Berikutnya (INP) situs Anda jika Anda memiliki tugas yang tidak memerlukan akses langsung ke thread utama. Namun, ringkasan saja tidak cukup, dan dalam modul ini, kasus penggunaan konkret untuk web worker ditawarkan.

Salah satu kasus penggunaan tersebut adalah situs yang perlu menghapus metadata Exif dari gambar—konsep ini tidak terlalu sulit dipahami. Faktanya, situs seperti Flickr menawarkan cara kepada pengguna untuk melihat metadata Exif guna mempelajari detail teknis tentang gambar yang dihostingnya, seperti kedalaman warna, merek dan model kamera, serta data lainnya.

Namun, logika untuk mengambil gambar, mengonversinya ke ArrayBuffer, dan mengekstrak metadata Exif berpotensi mahal jika dilakukan sepenuhnya di thread utama. Untungnya, cakupan pekerja web memungkinkan pekerjaan ini dilakukan di luar thread utama. Kemudian, menggunakan pipeline pesan web worker, metadata Exif dikirim kembali ke thread utama sebagai string HTML, dan ditampilkan kepada pengguna.

Tampilan thread utama tanpa pekerja web

Pertama, amati tampilan thread utama saat kita melakukan pekerjaan ini tanpa pekerja web. Caranya, lakukan langkah-langkah berikut:

  1. Buka tab baru di Chrome, lalu buka DevTools-nya.
  2. Buka panel performa.
  3. Buka https://chrome.dev/learn-performance-exif-worker/without-worker.html.
  4. Di panel performa, klik Rekam di sudut kanan atas panel DevTools.
  5. Tempelkan link gambar ini—atau link lain pilihan Anda yang berisi metadata Exif—di kolom dan klik tombol Dapatkan JPEG itu!.
  6. Setelah antarmuka diisi dengan metadata Exif, klik Rekam lagi untuk berhenti merekam.
Profiler performa yang menampilkan aktivitas aplikasi ekstraktor metadata gambar yang terjadi sepenuhnya di thread utama. Ada dua tugas panjang yang substansial—satu yang menjalankan pengambilan untuk mendapatkan gambar yang diminta dan mendekodenya, dan yang lainnya yang mengekstrak metadata dari gambar.
Aktivitas thread utama di aplikasi ekstraktor metadata gambar. Perhatikan bahwa semua aktivitas terjadi di thread utama.

Perhatikan bahwa—selain thread lain yang mungkin ada, seperti thread rasterizer dan sebagainya—semua yang ada di aplikasi terjadi di thread utama. Di thread utama, hal berikut terjadi:

  1. Formulir mengambil input dan mengirimkan permintaan fetch untuk mendapatkan bagian awal gambar yang berisi metadata Exif.
  2. Data gambar dikonversi menjadi ArrayBuffer.
  3. Skrip exif-reader digunakan untuk mengekstrak metadata Exif dari gambar.
  4. Metadata di-scrap untuk membuat string HTML, yang kemudian mengisi penampil metadata.

Sekarang bandingkan dengan penerapan perilaku yang sama—tetapi menggunakan web worker.

Tampilan thread utama dengan pekerja web

Setelah melihat tampilan proses ekstraksi metadata Exif dari file JPEG di thread utama, lihat tampilannya saat web worker terlibat:

  1. Buka tab lain di Chrome, lalu buka DevTools-nya.
  2. Buka panel performa.
  3. Buka https://chrome.dev/learn-performance-exif-worker/with-worker.html.
  4. Di panel performa, klik tombol rekam di pojok kanan atas panel DevTools.
  5. Tempelkan link gambar ini di kolom, lalu klik tombol Get that JPEG!.
  6. Setelah antarmuka diisi dengan metadata Exif, klik tombol rekam lagi untuk berhenti merekam.
Profiler performa yang menampilkan aktivitas aplikasi ekstraktor metadata gambar yang terjadi di thread utama dan thread pekerja web. Meskipun masih ada tugas panjang di thread utama, tugas tersebut jauh lebih singkat, dengan pengambilan/dekode gambar dan ekstraksi metadata terjadi sepenuhnya di thread pekerja web. Satu-satunya tugas thread utama adalah meneruskan data ke dan dari pekerja web.
Aktivitas thread utama di aplikasi ekstraktor metadata gambar. Perhatikan bahwa ada thread pekerja web tambahan tempat sebagian besar pekerjaan dilakukan.

Inilah kekuatan pekerja web. Daripada melakukan semuanya di thread utama, semuanya kecuali mengisi penampil metadata dengan HTML dilakukan di thread terpisah. Artinya, thread utama dibebaskan untuk melakukan tugas lain.

Keuntungan terbesar di sini adalah, tidak seperti versi aplikasi ini yang tidak menggunakan pekerja web, skrip exif-reader tidak dimuat di thread utama, melainkan di thread pekerja web. Artinya, biaya download, penguraian, dan kompilasi skrip exif-reader dilakukan di luar thread utama.

Sekarang mari kita pelajari kode pekerja web yang memungkinkan semua ini terjadi.

Melihat kode pekerja web

Tidak cukup hanya melihat perbedaan yang dihasilkan web worker, tetapi juga membantu memahami—setidaknya dalam kasus ini—tampilan kode tersebut sehingga Anda mengetahui apa yang mungkin terjadi dalam cakupan web worker.

Mulai dengan kode thread utama yang harus terjadi sebelum pekerja web dapat berperan:

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

Kode ini berjalan di thread utama, dan menyiapkan formulir untuk mengirim URL gambar ke pekerja web. Dari sana, kode pekerja web dimulai dengan pernyataan importScripts yang memuat skrip exif-reader eksternal, lalu menyiapkan pipeline pesan ke thread utama:

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

JavaScript ini menyiapkan pipeline pesan sehingga saat pengguna mengirimkan formulir dengan URL ke file JPEG, URL tersebut akan tiba di web worker. Dari sana, bagian kode berikutnya ini mengekstrak metadata Exif dari file JPEG, membuat string HTML, dan mengirimkan HTML tersebut kembali ke window untuk akhirnya ditampilkan kepada pengguna:

// 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://chrome.dev/learn-performance-exif-worker/js/with-worker/exif-worker.js
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('')
      });
    });
  });
});

Memang agak panjang, tetapi ini juga merupakan kasus penggunaan yang cukup rumit untuk pekerja web. Namun, hasilnya sebanding dengan upaya yang dilakukan, dan tidak hanya terbatas pada kasus penggunaan ini. Anda dapat menggunakan pekerja web untuk berbagai hal, seperti mengisolasi panggilan fetch dan memproses respons, memproses data dalam jumlah besar tanpa memblokir thread utama—dan itu baru permulaan.

Saat meningkatkan performa aplikasi web, mulailah memikirkan apa pun yang dapat dilakukan secara wajar dalam konteks pekerja web. Peningkatan yang didapat bisa signifikan, dan dapat menghasilkan pengalaman pengguna yang lebih baik secara keseluruhan untuk situs Anda.