在上個單元中,我們提供了網路工作站總覽。網路工作站可以將 JavaScript 從主執行緒移至獨立的網路工作站執行緒,藉此改善輸入回應速度,這樣當工作不需要直接存取主執行緒時,也能改善您網站的與 Next Paint 互動 (INP) 的互動。然而,單憑總覽並不足夠,本單元也提供網路工作站的具體用途。
這類使用案例可能是需要從圖片中移除 Exif 中繼資料的網站,但這不是這麼遠的概念。事實上,Flickr 等網站可讓使用者查看 Exif 中繼資料,藉此瞭解所代管圖片的技術詳細資料,例如色彩深度、相機廠牌和型號,以及其他資料。
但是,如果完全在主執行緒上完成擷取圖片、將其轉換為 ArrayBuffer
及擷取 Exif 中繼資料,則擷取圖片的邏輯可能會耗用大量資源。幸好,網路工作站範圍允許這項工作在主執行緒上完成。接著,透過網路工作站的訊息管道,Exif 中繼資料會以 HTML 字串形式傳回至主執行緒,並向使用者顯示。
不使用網路工作站的主執行緒外觀
首先,請觀察主執行緒在沒有網路工作站的情況下進行這項工作時的外觀。如要這樣做,請按照下列步驟操作:
- 在 Chrome 中開啟新分頁,然後開啟開發人員工具。
- 開啟效能面板。
- 前往 https://exif-worker.glitch.me/without-worker.html。
- 在效能面板中,按一下開發人員工具窗格右上角的「Record」。
- 將這張圖片連結或您選擇的其他含有 Exif 中繼資料的連結貼到欄位中,然後按一下「Get the JPEG!」按鈕。
- 介面填入 Exif 中繼資料後,再次按一下「Record」即可停止記錄。
請注意,除了可能出現的其他執行緒 (例如光柵化執行緒等) 之外,應用程式中的所有項目都會在主執行緒上進行。在主執行緒上,會發生以下情況:
- 這份表單會接收輸入內容並分派
fetch
要求,以取得包含 Exif 中繼資料的圖片初始部分。 - 圖片資料會轉換為
ArrayBuffer
。 exif-reader
指令碼可用來從圖片中擷取 Exif 中繼資料。- 系統會抓取中繼資料以建構 HTML 字串,然後填入中繼資料檢視器。
相較之下,採用相同行為,但使用網路工作站!
網路工作站「使用」主執行緒的流程
現在您已經瞭解到如何在主要執行緒上的 JPEG 檔案中擷取 Exif 中繼資料,請查看網路工作站在組合中的模樣:
- 在 Chrome 中開啟另一個分頁,然後開啟相應的開發人員工具。
- 開啟效能面板。
- 前往 https://exif-worker.glitch.me/with-worker.html。
- 在效能面板中,按一下開發人員工具窗格右上角的記錄按鈕。
- 將這張圖片連結貼到欄位中,然後按一下「Get this JPEG!」按鈕。
- 介面填入 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
呼叫和處理回應、在不封鎖主執行緒的情況下處理大量資料,這僅適用於新手。
改善網頁應用程式的效能時,請開始思考可在網路工作站環境中合理完成的任何事項。這麼做或許極大,還能提升網站的整體使用者體驗。