具體的網路工作人員用途

在上個單元中,我們提供了網路工作站總覽。網路工作站可以將 JavaScript 從主執行緒移至獨立的網路工作站執行緒,藉此改善輸入回應速度,這樣當工作不需要直接存取主執行緒時,也能改善您網站的與 Next Paint 互動 (INP) 的互動。然而,單憑總覽並不足夠,本單元也提供網路工作站的具體用途。

這類使用案例可能是需要從圖片中移除 Exif 中繼資料的網站,但這不是這麼遠的概念。事實上,Flickr 等網站可讓使用者查看 Exif 中繼資料,藉此瞭解所代管圖片的技術詳細資料,例如色彩深度、相機廠牌和型號,以及其他資料。

但是,如果完全在主執行緒上完成擷取圖片、將其轉換為 ArrayBuffer 及擷取 Exif 中繼資料,則擷取圖片的邏輯可能會耗用大量資源。幸好,網路工作站範圍允許這項工作在主執行緒上完成。接著,透過網路工作站的訊息管道,Exif 中繼資料會以 HTML 字串形式傳回至主執行緒,並向使用者顯示。

不使用網路工作站的主執行緒外觀

首先,請觀察主執行緒在沒有網路工作站的情況下進行這項工作時的外觀。如要這樣做,請按照下列步驟操作:

  1. 在 Chrome 中開啟新分頁,然後開啟開發人員工具。
  2. 開啟效能面板
  3. 前往 https://exif-worker.glitch.me/without-worker.html
  4. 在效能面板中,按一下開發人員工具窗格右上角的「Record」
  5. 這張圖片連結或您選擇的其他含有 Exif 中繼資料的連結貼到欄位中,然後按一下「Get the JPEG!」按鈕。
  6. 介面填入 Exif 中繼資料後,再次按一下「Record」即可停止記錄。
效能分析器,顯示完全在主執行緒上發生的圖片中繼資料擷取器應用程式活動。其中有兩個長時間的工作:一個執行擷取以取得要求的圖片並解碼,另一個則從圖片中擷取中繼資料。
圖片中繼資料擷取器應用程式中的主要執行緒活動。請注意,所有活動都會在主執行緒上進行。

請注意,除了可能出現的其他執行緒 (例如光柵化執行緒等) 之外,應用程式中的所有項目都會在主執行緒上進行。在主執行緒上,會發生以下情況:

  1. 這份表單會接收輸入內容並分派 fetch 要求,以取得包含 Exif 中繼資料的圖片初始部分。
  2. 圖片資料會轉換為 ArrayBuffer
  3. exif-reader 指令碼可用來從圖片中擷取 Exif 中繼資料。
  4. 系統會抓取中繼資料以建構 HTML 字串,然後填入中繼資料檢視器。

相較之下,採用相同行為,但使用網路工作站!

網路工作站「使用」主執行緒的流程

現在您已經瞭解到如何在主要執行緒上的 JPEG 檔案中擷取 Exif 中繼資料,請查看網路工作站在組合中的模樣:

  1. 在 Chrome 中開啟另一個分頁,然後開啟相應的開發人員工具。
  2. 開啟效能面板
  3. 前往 https://exif-worker.glitch.me/with-worker.html
  4. 在效能面板中,按一下開發人員工具窗格右上角的記錄按鈕
  5. 這張圖片連結貼到欄位中,然後按一下「Get this JPEG!」按鈕。
  6. 介面填入 Exif 中繼資料後,再次按一下記錄按鈕即可停止記錄。
效能分析器,顯示圖片中繼資料擷取器應用程式的活動,同時在主執行緒和網路背景工作執行緒上發生。雖然主執行緒上仍存在長時間工作,但這些工作的時間更短,因為圖片擷取/解碼和中繼資料擷取完全是在網路背景工作執行緒上進行。唯一的主執行緒工作涉及將資料傳入及傳出網路工作站。
圖片中繼資料擷取器應用程式中的主要執行緒活動。請注意,還有其他網路工作執行緒執行緒會完成大部分工作。

這是網路工作站的強大威力。雖然在主執行緒上執行「所有內容」,但使用 HTML 填入中繼資料檢視器的一切作業是在獨立的執行緒上完成。這表示主執行緒釋出空間給其他工作。

這可能最大的優勢在於,與不使用網路工作站的應用程式版本不同,exif-reader 指令碼不會在主執行緒上載入,而是在網路背景工作執行緒上載入。這表示下載、剖析及編譯 exif-reader 指令碼的費用會從主執行緒之外進行。

現在,讓我們來深入探討網路工作程式程式碼,掌握全貌!

瞭解網路工作站程式碼

光是看到網路工作站所創造的差異還不夠,至少也建議您瞭解 (在本案例中) 程式碼的樣貌,讓您瞭解網路工作站範圍中可能存在的內容。

先從網路工作站輸入圖片前所需的主要執行緒程式碼開始:

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

此程式碼會在主執行緒上執行,並設定表單,將圖片網址傳送至網路工作站。接著,網路工作站程式碼會從 importScripts 陳述式開始,此陳述式會載入外部 exif-reader 指令碼,然後設定訊息管道到主執行緒:

// 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 會設定訊息管道,如此一來,當使用者在表單中提交 JPEG 檔案的網址時,網址就會傳送至網路工作站。之後,接下來這段程式碼會從 JPEG 檔案擷取 Exif 中繼資料、建構 HTML 字串,然後將該 HTML 傳回 window,最後再向使用者顯示:

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

這有點易讀,但對網路工作處理人員而言,也是相當涉及使用情境。 不過,結果值得努力,不單只是這個用途而已。您可以使用網路工作站處理各種事務,例如隔離 fetch 呼叫和處理回應、在不封鎖主執行緒的情況下處理大量資料,這僅適用於新手。

改善網頁應用程式的效能時,請開始思考可在網路工作站環境中合理完成的任何事項。這麼做或許極大,還能提升網站的整體使用者體驗。