JavaScript/Web Worker API

Web Worker API

はじめに

JavaScriptは基本的にシングルスレッドで動作します。これはメインスレッドが重い処理を実行している間、ユーザーインターフェースがフリーズしてしまう可能性があるという問題があります。Web Worker APIはこの問題を解決するための技術で、バックグラウンドでJavaScriptを実行するための標準的な方法を提供します。

Web Workerの基本

Web Workerは、メインスレッドとは別のバックグラウンドスレッドでJavaScriptコードを実行できるようにします。これにより、複雑な計算や時間のかかる処理をメインスレッドをブロックすることなく実行できます。

main.js
(メインスレッド)
// Workerを作成
const myWorker = new Worker('worker.js');

// Workerにメッセージを送信
myWorker.postMessage({number: 1000000});

// Workerからのメッセージを受信
myWorker.onmessage = function(e) {
  console.log('計算結果: ' + e.data.result);
};

// エラーハンドリング
myWorker.onerror = function(error) {
  console.error('Worker error: ' + error.message);
};
worker.js
(ワーカースレッド)
// メインスレッドからのメッセージを受信
self.onmessage = function(e) {
  const number = e.data.number;
  
  // 時間のかかる計算(例:素数判定)
  let result = findPrimes(number);
  
  // 結果をメインスレッドに送り返す
  self.postMessage({result: result});
};

// 指定した数値までの素数を見つける関数
function findPrimes(max) {
  const primes = [];
  for (let i = 2; i <= max; i++) {
    let isPrime = true;
    for (let j = 2; j <= Math.sqrt(i); j++) {
      if (i % j === 0) {
        isPrime = false;
        break;
      }
    }
    if (isPrime) primes.push(i);
  }
  return primes.length; // 素数の数を返す
}

通信の仕組み

Web Workerとメインスレッド間の通信は、postMessage()メソッドとonmessageイベントハンドラを使用して行います。データは値のコピーとして渡され、共有されません。

機能 メインスレッド ワーカースレッド
メッセージ送信 worker.postMessage(data) self.postMessage(data)
メッセージ受信 worker.onmessage = function(e) {...} self.onmessage = function(e) {...}
エラー処理 worker.onerror = function(e) {...} self.onerror = function(e) {...}
終了 worker.terminate() self.close()

Web Workerの種類

専用Worker (Dedicated Worker)

一つのスクリプトからのみ利用可能なワーカーです。最も一般的なタイプのワーカーです。

// メインスクリプト
const dedicatedWorker = new Worker('worker.js');
dedicatedWorker.postMessage('Hello from main thread');

共有Worker (Shared Worker)

複数のスクリプト(ウィンドウ、タブ、フレームなど)から利用可能なワーカーです。

// メインスクリプト
const sharedWorker = new SharedWorker('shared-worker.js');
sharedWorker.port.start(); // 通信を開始
sharedWorker.port.postMessage('Hello from window 1');

// shared-worker.js
self.onconnect = function(e) {
  const port = e.ports[0]; // 接続してきたポートを取得
  
  port.onmessage = function(e) {
    // メッセージを受信し処理
    port.postMessage('Received: ' + e.data);
  };
  
  port.start(); // 通信を開始
};

Web Workerの制限事項

Workerでは利用できないAPIがいくつかあります。

利用できないもの 理由
DOM操作 (document, window) UIスレッドのみが担当
親ページの変数・関数へのアクセス 別スレッドで実行されているため
alert(), confirm() ユーザーインタラクションはメインスレッドのみ

代わりに、Workerでは以下のAPIが利用可能です:

利用可能なAPI
Web Worker固有API self, postMessage()
XMLHttpRequest ネットワークリクエスト
WebSocket 双方向通信
タイマー setTimeout(), setInterval()
Application Cache オフラインアクセス
インポート機能 importScripts()

Transferable Objectsによる効率的なデータ転送

大量のデータを扱う場合は、データのコピーではなく所有権の移転によって効率的に通信できます。

// メインスレッド
const arrayBuffer = new ArrayBuffer(1024 * 1024 * 32); // 32MB
// データの所有権をWorkerに移転
worker.postMessage({ data: arrayBuffer }, [arrayBuffer]);
// このあとarrayBufferは使用できなくなる(長さが0になる)
console.log(arrayBuffer.byteLength); // 0

// Workerスレッド
self.onmessage = function(e) {
  const receivedBuffer = e.data.data;
  console.log(receivedBuffer.byteLength); // 33554432 (32MB)
  
  // 処理後、メインスレッドに所有権を戻す
  self.postMessage({ result: receivedBuffer }, [receivedBuffer]);
};

Web Workerのユースケース

Web Workerは以下のような場面で特に有用です:

  1. 画像・音声・動画処理
  2. データの解析と加工
  3. 複雑な計算処理
  4. 大量のJSON処理
  5. 暗号化・復号化処理

実用的な例:画像処理

main.js
const imageProcessingWorker = new Worker('image-processor.js');
const canvas = document.getElementById('sourceCanvas');
const ctx = canvas.getContext('2d');
const image = document.getElementById('sourceImage');

// 画像をロード
image.onload = function() {
  canvas.width = image.width;
  canvas.height = image.height;
  ctx.drawImage(image, 0, 0);
  
  // キャンバスから画像データを取得
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  
  // ワーカーに処理を依頼
  imageProcessingWorker.postMessage({
    imageData: imageData,
    filter: 'grayscale'
  }, [imageData.data.buffer]);  // Transferable Objectsを使用
};

// 処理結果を受け取る
imageProcessingWorker.onmessage = function(e) {
  const resultImageData = e.data.imageData;
  
  // 結果を新しいキャンバスに描画
  const resultCanvas = document.getElementById('resultCanvas');
  const resultCtx = resultCanvas.getContext('2d');
  resultCanvas.width = resultImageData.width;
  resultCanvas.height = resultImageData.height;
  resultCtx.putImageData(resultImageData, 0, 0);
};

// image-processor.js (ワーカー)
self.onmessage = function(e) {
  const imageData = e.data.imageData;
  const filter = e.data.filter;
  
  // フィルタ処理を実行
  switch (filter) {
    case 'grayscale':
      applyGrayscale(imageData);
      break;
    case 'invert':
      applyInvert(imageData);
      break;
    // 他のフィルタケース
  }
  
  // 処理結果を返す
  self.postMessage({ imageData: imageData }, [imageData.data.buffer]);
};

// グレースケール変換処理
function applyGrayscale(imageData) {
  const data = imageData.data;
  for (let i = 0; i < data.length; i += 4) {
    const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
    data[i] = avg;     // R
    data[i + 1] = avg; // G
    data[i + 2] = avg; // B
    // data[i + 3]はアルファ値(透明度)なので変更しない
  }
}

// 色反転処理
function applyInvert(imageData) {
  const data = imageData.data;
  for (let i = 0; i < data.length; i += 4) {
    data[i] = 255 - data[i];         // R
    data[i + 1] = 255 - data[i + 1]; // G
    data[i + 2] = 255 - data[i + 2]; // B
  }
}

パフォーマンス考慮事項

Web Workerを効果的に使用するためのポイントをまとめます:

  1. メッセージの送受信にはオーバーヘッドがあります。頻繁に小さなデータを送るよりも、まとめて送る方が効率的です。
  2. 複雑な計算処理や長時間実行される処理はWorkerに移動しましょう。
  3. 大きなデータを扱う場合は、Transferable Objectsを使用してパフォーマンスを向上させましょう。
  4. 処理の進捗状況を定期的にメインスレッドに報告することで、UXを向上させられます。

まとめ

Web Worker APIは、JavaScriptの単一スレッド制約を克服し、複雑な処理をバックグラウンドで実行することを可能にします。適切に使用することで、レスポンシブなWebアプリケーションを構築できます。メッセージング、共有データ、エラー処理などの機能を理解し、実践的なシナリオで適用することが重要です。

この章で紹介したコード例を元に、実際のプロジェクトでWeb Workerを導入してみてください。特に時間のかかる処理や計算負荷の高い機能がある場合、顕著なパフォーマンス向上が期待できます。

カテゴリ:JavaScript カテゴリ:HTML カテゴリ:Application Programming Interface
カテゴリ:Application Programming Interface カテゴリ:HTML カテゴリ:JavaScript