Web Worker を使ってバックグラウンド処理などしてみる

先日 Service Worker と Push API を使ったバックグラウンド・プッシュ通知処理を紹介したが、今回はその Service Worker の根幹の仕組みである Web Worker を扱ってみる。

コチラの方がより原始的で、ブラウザとネットワークを仲介する Service Worker よりも構造がシンプルなので、理解しやすいと思う。

Web Worker はメインの JavaScript スレッド (ブラウザでウェブページとして読み込まれる世界) とは別のスレッドを立て、双方はメッセージ通信によってやり取りができる。スレッドが異なるので Web Worker 側で重たい処理をしていてもブラウザ側がフリーズしたりすることはない。Service Worker と違ってタブを閉じると自動的に Web Worker も終了するが、複数立ち上げたりなどもできるので、バックグラウンドで何か重たい処理をやらせるのに使えるだろう。

というワケでサンプルを作ってみた。今回は重たい処理などはせず、setTimeout で応答を遅延させただけの、単純なテキストメッセージのやり取りのみ。

ブラウザ側ではこんな感じで Web Worker を起動して、メッセージを受け取れるように待機する。

const webWorker = new Worker('./web-worker.js');

webWorker.addEventListener('message', event => {
  console.log('Web Worker からメッセージを受信', event, event.data);
});
webWorker.addEventListener('error', error => {
  console.warn('Web Worker でエラーが発生', error);
});

// Web Worker にメッセージを送信する
webWorker.postMessage('Hello World');

// Web Worker を任意のタイミングで終了させるには…
webWorker.terminate();

一方 Web Worker 側のコードはこんな感じ。Service Worker と同様に、window は存在せず self がグローバル変数になっている。

self.addEventListener('message', event => {
  console.log('メインスレッドからメッセージを受信', event, event.data);
  
  self.postMessage('Web Worker からメインスレッドに返信します');
});

// 自身で終了するには…
self.close();

非同期処理や async も使えることは使えるのだが、async の内部でエラーが発生すると、webWorker.addEventListener('error')webWorker.onerror = () => {} でエラーイベントを検知できなくなってしまった。unhandledrejection イベントなのかと思ったらそれだけでもないようで。回避策はあるようなのだが、面倒なので今回はサボる。w

ついでに、Web Worker は別 JS ファイルに分割しなくても起動する方法がある。Web Worker のコードを Blob URL 化する方法だ。

// Web Worker のコードを文字列として持っておく
const webWorkerCode = `
  self.addEventListener('message', event => {
    console.log('メインスレッドからメッセージを受信', event, event.data);
    self.postMessage('メインスレッドに返信');
  });
`;
const webWorkerBlob = new Blob([webWorkerCode]);
const webWorkerBlobUrl = URL.createObjectURL(webWorkerBlob);
const webWorker = new Worker(webWorkerBlobUrl);

こんな感じ。小さなコードならこういうやり方で単一 HTML ファイルとして Web Worker を提供するのもアリかも。