Web Worker を使ってバックグラウンド処理などしてみる
先日 Service Worker と Push API を使ったバックグラウンド・プッシュ通知処理を紹介したが、今回はその Service Worker の根幹の仕組みである Web Worker を扱ってみる。
コチラの方がより原始的で、ブラウザとネットワークを仲介する Service Worker よりも構造がシンプルなので、理解しやすいと思う。
Web Worker はメインの JavaScript スレッド (ブラウザでウェブページとして読み込まれる世界) とは別のスレッドを立て、双方はメッセージ通信によってやり取りができる。スレッドが異なるので Web Worker 側で重たい処理をしていてもブラウザ側がフリーズしたりすることはない。Service Worker と違ってタブを閉じると自動的に Web Worker も終了するが、複数立ち上げたりなどもできるので、バックグラウンドで何か重たい処理をやらせるのに使えるだろう。
というワケでサンプルを作ってみた。今回は重たい処理などはせず、setTimeout
で応答を遅延させただけの、単純なテキストメッセージのやり取りのみ。
- デモ : Practice Web Worker
- コード : frontend-sandboxes/index.html at master · Neos21/frontend-sandboxes
- コード : frontend-sandboxes/web-worker.js at master · Neos21/frontend-sandboxes
ブラウザ側ではこんな感じで 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
- 参考 : javascript - How to bubble a web worker error in a promise via worker.onerror? - Stack Overflow
- 参考 : javascript - Catching errors inside of web worker inside of eval inside of async function? - Stack Overflow
- 参考 : Unhandled rejections in workers should not fire an
error
event on theWorker
object · Issue #12221 · denoland/deno
ついでに、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 を提供するのもアリかも。