AngularでWebWorkerを使ったバックグラウンドで生きるSetInterval
JavaScriptでは、一定の間隔で関数やコードスニペットを繰り返し呼び出す用のSetIntervalというWebAPIが使用できます。
こちらをログなどのため、スマホのバックグラウンドでも動かしたいという要件があったのですが、
バックグラウンドのタブによる負荷(および関連するバッテリーの使用量)を軽減するために、ブラウザはアクティブでないタブの最小タイムアウト時間を強制します。
アクティブでないタブの最小タイムアウトは 1 秒です。
などの理由でアクティブなタブとそうでないタブで動作が異なることが分かりました。(実際の実装でも、バックグラウンドでの動作が不安定でした)
そのため、WebWorkerを使うことで、バックグラウンドでもアクティブなタブの時のように指定した秒数でタイマーが動くように実装したのでメモします。
WebWorkerとは
Web Worker とは、ウェブアプリケーションにおけるスクリプトの処理をメインとは別のスレッドに移し、バックグラウンドでの実行を可能にする仕組みのことです。時間のかかる処理を別のスレッドに移すことが出来るため、 UI を担当するメインスレッドの処理を中断・遅延させずに実行できるという利点があります。
とあります。
AngularでのWebWorkerの作り方
Angularでは、次のngコマンドでWebWorkerを生成することができます。
ng generate web-worker workers/set-interval(対象ディレクトリor使うcomponentパス/Worker名)
Workerを使ったSetInterval
その後生成されたxx.worker.tsを次のように書き換えます。
// アプリケーション側からpostMessageされたものが送られる addEventListener('message', ({ data }) => { const intervalTime = event.data.intervalTime; if (intervalTime) { // WorkerスレッドでsetIntervalする setInterval(() => { postMessage(null); }, intervalTime) as unknown as number; } });
そして該当ServiceやComponentから次のように設定します
worker: Worker = null; // 2分に設定 intervalTime = 2 * 60 * 1000; intervalId: number = null; // typeof window.setInterval if (typeof Worker !== 'undefined') { // ワーカーを作成 this.worker = new Worker(new URL('../workers/set-interval.worker', import.meta.url)); // Worker側からpostMessageされたものが送られる this.worker.onmessage = () => { // 定期的に実行したい関数 callback(); }; // アプリケーション側から2分間隔で送るようにWorkerのSetIntervalを設定 this.worker.postMessage({intervalTime: this.intervalTime}); } else { // Web Workerがサポートされていない時の処理 this.intervalId = setInterval( callback, this.intervalTime, ) }
こういった実装をすることで、スマホのバックグラウンドやアクティブでないタブの時も、通常の動作のSetIntervalを設定することができます。
注意点としては、Workerは止めるか、Workerが存在するか、タブが閉じられない限り生き続けてしまい、メモリリークなどの原因となる場合があるため、必要がなくなったときは、次のようにworker.terminate();を呼ぶことで終了できます。
if (this.worker) { // terminateでworkerを強制終了、worker自体をnullに this.worker.terminate(); this.worker = null; } else if (this.intervalId) { // Web Workerがサポートされていない時に通常のsetIntervalをしていたらclearIntervalする clearInterval(this.intervalId); }
Worker.terminate() - Web API | MDN
まとめ
WebWorkerを使う機会がなく、初めて使いましたがWorkerスレッドでスマホのバックグラウンドでも実行したい動作を統一することや、メインスレッドの負担になるような重い処理を逃すことに使えるというのを実際に体験できました。 参考になれば嬉しいです。