「ページが応答しません」という悪夢とJavaScriptの限界
ユーザーが「レポート出力」ボタンをクリックした瞬間にWebサイトが固まってしまった経験はありませんか?画面がフリーズし、マウスカーソルが回転し続け、ブラウザが忌々しい 「ページが応答しません」 という通知を表示する。これはメインスレッド(Main Thread)が過負荷になった際の必然的な結果です。
本質的に、JavaScriptはシングルスレッド(Single-threaded)で動作します。UIのレンダリングからクリックイベントの取得、ロジックの計算まで、すべてが1つの道に並んで処理を待ちます。もし100万要素の配列を処理させようとすれば、その道は渋滞してしまいます。ブラウザにはUIを更新するためのリソースが残らず、ユーザー体験は最悪なものになります。
Web Workersこそがその救世主です。これはバックグラウンドスレッド(Background Thread)という「優先車線」を新たに作成することを可能にします。重いタスクはバックグラウンド処理させ、その間メインスレッドはユーザーのスクロールやクリック操作を軽快に処理し続けることができます。
Web Workersの実装:スレッドを分離してUIを解放する
嬉しいことに、Web Workersは標準APIとして提供されているため、重いライブラリを追加でインストールする必要はありません。ただし、Workerは完全に独立した環境で動作するため、コードを別のファイルに分ける必要があります。
Workerにデータを渡す前に、私はよく JSON Formatter を使って構造をチェックします。これにより、入力データが正しいことを確認し、Workerが途中でクラッシュするような単純なロジックエラーを防ぐことができます。
メインスレッド(main.js)とバックグラウンド処理(worker.js)の間の基本的な設定方法を見てみましょう。
ステップ1:main.jsでサブスレッドを初期化する
// main.js
if (window.Worker) {
const myWorker = new Worker('worker.js');
// 500,000件のレコードをバックグラウンド処理に送る
myWorker.postMessage(hugeDataset);
// Workerが完了した時に結果を受け取る
myWorker.onmessage = (e) => {
console.log('データ処理が完了しました:', e.data);
renderUI(e.data);
};
myWorker.onerror = (err) => console.error('Workerエラー:', err.message);
}
ステップ2:worker.jsに処理ロジックを記述する
重要な注意点:Worker内ではDOM(document, window)にアクセスできません。すべての操作は純粋なデータ処理のみとなります。
// worker.js
onmessage = function(e) {
const data = e.data;
// 重い処理のシミュレーション:100万アイテムをマップ処理
const result = data.map(item => {
let sum = 0;
for(let i = 0; i < 1000; i++) sum += i;
return { ...item, score: sum };
});
postMessage(result);
};
最適化:データ転送時のパフォーマンスの罠を避ける
デフォルトでは、ブラウザはスレッド間でデータをコピーするために Structured Clone アルゴリズムを使用します。もし100MBのJSONファイルを送信する場合、ブラウザは同じコピーを作成するだけでかなりのCPUとメモリを消費します。これが意図せず新たなボトルネックを生むことになります。
Transferable Objectsを使用して最高速度を実現する
バイト配列(ArrayBuffer)形式のデータでは、「所有権の譲渡」という仕組みを利用しましょう。コピーする代わりに、メインスレッドはメモリ領域のアドレスを直接Workerに渡します。譲渡後、メインスレッドはアクセス権を失いますが、転送速度はほぼ0msになります。
// コピーのリソースを消費せずに32MBのデータを譲渡する
const buffer = new ArrayBuffer(1024 * 1024 * 32);
myWorker.postMessage(buffer, [buffer]);
効果の検証:数字は嘘をつかない
パフォーマンスを推測するのではなく、Chrome DevToolsを開いて明確な違いを確認しましょう。Performance タブでは、Workerを使用する前後で劇的な変化が見られます。
- Workerなし: Main行に長い赤線が表示され、「Long Task」(通常50ms以上)を警告します。Webサイトがカクつきます。
- Workerあり: Main行は空いており、クリーンな状態です。重い計算ブロックは「Worker」という別の行に押し出されています。
実務上の経験から言うと、Web Workerは100ms以上かかるタスクにのみ使用すべきです。Worker의初期化にも一定のコスト(オーバーヘッド)がかかります。単純な数値をいくつか加算するだけなら、メインスレッドで直接実行する方が速い場合もあります。
大規模なプロジェクトでWorkerをよりプロフェッショナルに管理したい場合は、Comlink を試してみてください。このライブラリは、複雑な postMessage のやり取りを非常にスッキリとした async/await の関数呼び出しに変換してくれます。Web Workerは単なるツールではなく、計算と表示を切り離してプロフェッショナルなWebアプリケーションを作成するための思考法なのです。

