Trường hợp sử dụng cụ thể của web worker

Trong mô-đun cuối cùng, chúng ta đã có thông tin tổng quan về web worker. Web worker có thể cải thiện khả năng phản hồi của dữ liệu đầu vào bằng cách di chuyển JavaScript ra khỏi luồng chính sang các luồng web worker riêng biệt. Điều này có thể giúp cải thiện Tương tác đến lần hiển thị tiếp theo (INP) của trang web khi bạn có công việc không cần truy cập trực tiếp vào luồng chính. Tuy nhiên, chỉ có thông tin tổng quan là chưa đủ. Trong mô-đun này, chúng tôi sẽ cung cấp một trường hợp sử dụng cụ thể cho web worker.

Một trường hợp sử dụng như vậy có thể là một trang web cần xoá siêu dữ liệu Exif khỏi hình ảnh – đây không phải là một khái niệm quá xa vời. Trên thực tế, các trang web như Flickr cung cấp cho người dùng cách xem siêu dữ liệu Exif để tìm hiểu thông tin chi tiết về kỹ thuật của những hình ảnh mà họ lưu trữ, chẳng hạn như độ sâu màu, nhà sản xuất và mẫu máy ảnh, cũng như các dữ liệu khác.

Tuy nhiên, logic để tìm nạp hình ảnh, chuyển đổi hình ảnh đó thành ArrayBuffer và trích xuất siêu dữ liệu Exif có thể tốn nhiều tài nguyên nếu được thực hiện hoàn toàn trên luồng chính. Rất may là phạm vi web worker cho phép thực hiện công việc này bên ngoài luồng chính. Sau đó, bằng cách sử dụng quy trình truyền thông báo của web worker, siêu dữ liệu Exif sẽ được truyền trở lại luồng chính dưới dạng một chuỗi HTML và hiển thị cho người dùng.

Luồng chính trông như thế nào khi không có web worker

Trước tiên, hãy quan sát luồng chính khi chúng ta thực hiện thao tác này mà không có web worker. Để cập nhật, hãy thực hiện các bước sau:

  1. Mở một thẻ mới trong Chrome rồi mở Công cụ cho nhà phát triển của thẻ đó.
  2. Mở bảng điều khiển hiệu suất.
  3. Truy cập vào https://chrome.dev/learn-performance-exif-worker/without-worker.html.
  4. Trong bảng điều khiển hiệu suất, hãy nhấp vào Ghi lại ở góc trên bên phải của ngăn Công cụ cho nhà phát triển.
  5. Dán đường liên kết đến hình ảnh này (hoặc một đường liên kết khác mà bạn chọn có chứa siêu dữ liệu Exif) vào trường rồi nhấp vào nút Lấy JPEG đó!.
  6. Sau khi giao diện điền sẵn siêu dữ liệu Exif, hãy nhấp lại vào Record (Ghi) để dừng ghi.
Trình phân tích hiệu suất cho thấy hoạt động của ứng dụng trích xuất siêu dữ liệu hình ảnh diễn ra hoàn toàn trên luồng chính. Có 2 tác vụ dài quan trọng: một tác vụ chạy lệnh tìm nạp để lấy hình ảnh được yêu cầu và giải mã hình ảnh đó, còn tác vụ khác trích xuất siêu dữ liệu từ hình ảnh.
Hoạt động của luồng chính trong ứng dụng trích xuất siêu dữ liệu hình ảnh. Xin lưu ý rằng tất cả hoạt động đều diễn ra trên luồng chính.

Xin lưu ý rằng ngoài các luồng khác có thể xuất hiện (chẳng hạn như luồng rasterizer, v.v.), mọi thứ trong ứng dụng đều diễn ra trên luồng chính. Trên luồng chính, những điều sau sẽ xảy ra:

  1. Biểu mẫu này lấy dữ liệu đầu vào và gửi một yêu cầu fetch để lấy phần ban đầu của hình ảnh chứa siêu dữ liệu Exif.
  2. Dữ liệu hình ảnh được chuyển đổi thành ArrayBuffer.
  3. Tập lệnh exif-reader được dùng để trích xuất siêu dữ liệu Exif từ hình ảnh.
  4. Siêu dữ liệu được trích xuất để tạo một chuỗi HTML, sau đó điền vào trình xem siêu dữ liệu.

Bây giờ, hãy so sánh điều đó với một cách triển khai có cùng hành vi nhưng sử dụng một web worker!

Luồng chính trông như thế nào với một web worker

Giờ đây, bạn đã thấy những việc cần làm để trích xuất siêu dữ liệu Exif từ một tệp JPEG trên luồng chính. Hãy xem những việc cần làm khi có một worker trên web:

  1. Mở một thẻ khác trong Chrome rồi mở Công cụ cho nhà phát triển của thẻ đó.
  2. Mở bảng điều khiển hiệu suất.
  3. Truy cập vào https://chrome.dev/learn-performance-exif-worker/with-worker.html.
  4. Trong bảng điều khiển hiệu suất, hãy nhấp vào nút ghi ở góc trên bên phải của ngăn Công cụ cho nhà phát triển.
  5. Dán đường liên kết đến hình ảnh này vào trường rồi nhấp vào nút Get that JPEG! (Lấy JPEG đó!).
  6. Sau khi giao diện điền sẵn siêu dữ liệu Exif, hãy nhấp lại vào nút ghi để dừng ghi.
Trình phân tích hiệu suất cho thấy hoạt động của ứng dụng trích xuất siêu dữ liệu hình ảnh diễn ra trên cả luồng chính và luồng worker trên web. Mặc dù vẫn còn các tác vụ dài trên luồng chính, nhưng chúng ngắn hơn đáng kể, với quá trình tìm nạp/giải mã hình ảnh và trích xuất siêu dữ liệu diễn ra hoàn toàn trên một luồng worker web. Công việc duy nhất của luồng chính là truyền dữ liệu đến và đi từ web worker.
Hoạt động của luồng chính trong ứng dụng trích xuất siêu dữ liệu hình ảnh. Xin lưu ý rằng có một luồng worker bổ sung trên web, nơi hầu hết các thao tác được thực hiện.

Đây là sức mạnh của web worker. Thay vì thực hiện mọi thứ trên luồng chính, mọi thứ (ngoại trừ việc điền sẵn trình xem siêu dữ liệu bằng HTML) đều được thực hiện trên một luồng riêng. Điều này có nghĩa là luồng chính được giải phóng để thực hiện các công việc khác.

Có lẽ lợi thế lớn nhất ở đây là không giống như phiên bản của ứng dụng này không sử dụng web worker, tập lệnh exif-reader không được tải trên luồng chính mà được tải trên luồng web worker. Điều này có nghĩa là chi phí tải xuống, phân tích cú pháp và biên dịch tập lệnh exif-reader diễn ra ngoài luồng chính.

Bây giờ, hãy tìm hiểu mã web worker giúp mọi thứ trở nên có thể!

Xem xét mã trình thực thi web

Chỉ xem sự khác biệt mà web worker tạo ra là chưa đủ, bạn cũng cần hiểu (ít nhất là trong trường hợp này) mã đó trông như thế nào để biết những gì có thể xảy ra trong phạm vi web worker.

Bắt đầu bằng mã luồng chính cần xuất hiện trước khi web worker có thể tham gia vào quá trình này:

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

Mã này chạy trên luồng chính và thiết lập biểu mẫu để gửi URL hình ảnh đến web worker. Từ đó, mã web worker bắt đầu bằng câu lệnh importScripts tải tập lệnh exif-reader bên ngoài, sau đó thiết lập quy trình truyền thông báo đến luồng chính:

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

Đoạn mã JavaScript này thiết lập quy trình truyền thông báo để khi người dùng gửi biểu mẫu có URL đến một tệp JPEG, URL đó sẽ đến web worker. Từ đó, đoạn mã tiếp theo này sẽ trích xuất siêu dữ liệu Exif từ tệp JPEG, tạo một chuỗi HTML và gửi chuỗi HTML đó trở lại window để cuối cùng hiển thị cho người dùng:

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

Bạn sẽ phải đọc một chút, nhưng đây cũng là một trường hợp sử dụng khá phức tạp đối với web worker. Tuy nhiên, kết quả thu được xứng đáng với công sức bỏ ra và không chỉ giới hạn ở trường hợp sử dụng này. Bạn có thể sử dụng web worker cho mọi loại hoạt động, chẳng hạn như cách ly các lệnh gọi fetch và xử lý các phản hồi, xử lý một lượng lớn dữ liệu mà không chặn luồng chính – và đó chỉ là bước khởi đầu.

Khi cải thiện hiệu suất của các ứng dụng web, hãy bắt đầu nghĩ về mọi thứ có thể thực hiện một cách hợp lý trong ngữ cảnh của web worker. Những lợi ích này có thể rất đáng kể và có thể mang lại trải nghiệm tổng thể tốt hơn cho người dùng trên trang web của bạn.