前のモジュールでは、ウェブワーカーの概要について学習しました。ウェブワーカーは、JavaScript をメインスレッドから別のウェブワーカー スレッドに移動することで入力の応答性を改善できます。これにより、メインスレッドに直接アクセスする必要がない処理がある場合に、ウェブサイトの Interaction to Next Paint(INP)を改善できます。ただし、概要だけでは不十分です。このモジュールでは、ウェブワーカーの具体的なユースケースについて説明します。
そのようなユースケースの 1 つが、画像から Exif メタデータを削除する必要があるウェブサイトです。これは、あまり知られていないコンセプトではありません。実際、Flickr のようなウェブサイトでは、Exif メタデータを表示して、色深度、カメラのメーカー、モデルなど、ホストする画像の技術的な詳細を知ることができます。
ただし、画像の取得、ArrayBuffer
への変換、Exif メタデータの抽出などのロジックをすべてメインスレッドで行うと、コストが高くなる可能性があります。幸いなことに、ウェブワーカー スコープを使用すると、この処理をメインスレッドの外部で行うことができます。その後、ウェブワーカーのメッセージング パイプラインを使用して、Exif メタデータが HTML 文字列としてメインスレッドに送り返され、ユーザーに表示されます。
ウェブ ワーカーを使用しない場合のメインスレッド
まず、ウェブワーカーを使用せずにこの作業を行う場合、メインスレッドがどのように表示されるかを確認します。手順は次のとおりです。
- Chrome で新しいタブを開き、DevTools を開きます。
- パフォーマンス パネルを開きます。
- https://exif-worker.glitch.me/without-worker.html に移動します。
- パフォーマンス パネルで、DevTools ペインの右上にある [Record] をクリックします。
- こちらの画像リンク(または Exif メタデータを含む別の画像リンク)をフィールドに貼り付け、[Get that JPEG!] ボタンをクリックします。
- Exif メタデータがインターフェースで入力されたら、もう一度 [Record] をクリックして記録を停止します。

ラスタライザ スレッドなど、他のスレッドが存在する可能性がある場合を除き、アプリ内のあらゆるものはメインスレッドで発生します。メインスレッドでは、次の処理が行われます。
- このフォームは入力を受け取り、
fetch
リクエストを送信して、Exif メタデータを含む画像の最初の部分を取得します。 - 画像データは
ArrayBuffer
に変換されます。 exif-reader
スクリプトは、イメージから Exif メタデータを抽出するために使用されます。- メタデータがスクレイピングされて HTML 文字列が作成され、メタデータ ビューアに入力されます。
これとは対照的に、同じ動作を実装していますが、ウェブワーカーを使用します。
ウェブワーカーを使用したメインスレッド
ここまでで、メインスレッドで JPEG ファイルから Exif メタデータを抽出する例を見てきました。次は、ウェブワーカーが混在する状況を見てみましょう。
- Chrome で別のタブを開き、DevTools を開きます。
- パフォーマンス パネルを開きます。
- https://exif-worker.glitch.me/with-worker.html に移動します。
- パフォーマンス パネルで、DevTools ペインの右上隅にある記録ボタンをクリックします。
- フィールドにこの画像リンクを貼り付け、[Get that 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';
});
このコードはメインスレッドで実行され、画像の URL をウェブワーカーに送信するためのフォームを設定します。ウェブワーカーコードは、外部 exif-reader
スクリプトを読み込み、メインスレッドへのメッセージ パイプラインを設定する importScripts
ステートメントで開始します。
// 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 ファイルへの URL を含むフォームを送信すると、URL がウェブワーカーに到着するようにメッセージング パイプラインが設定されます。次に、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
呼び出しの分離やレスポンスの処理、メインスレッドをブロックせずに大量のデータを処理するなど、あらゆることに使用できます。これは開始条件にすぎません。
ウェブ アプリケーションのパフォーマンスを向上させる場合は、ウェブワーカーのコンテキストで合理的に実行できることを検討します。こうしたメリットは大きく、ウェブサイトの全体的なユーザー エクスペリエンスの向上につながる可能性があります。