マルチスレッド: Web Worker

JavaScript は基本シングルスレッド(UIスレッド)でイベントループを回しつつ処理を実行してます。UIスレッドで重い処理を行うと一時的に画面がフリーズしたり、画面が表示されるまでの速度が遅くなったりの原因になります。

Web Worker を使うと JavaScript コードをUIとは別のスレッドで実行できるので、そういった問題を回避できるかもしれません。ここにフリーズする例しない例があります。このしない例では Web Worker を使用してます。

PromisesetTimeoutなどとの違いは、それらは非同期で実行されますが UIスレッド上で処理されます。Web Worker も非同期ですが別のスレッドで処理されるという違いがあります。

仕組み

Web Worker はイベントで UIスレッドとのデータのやり取りを行います。どちらのスレッドでも使うのは2つのメソッドだけです。

  1. #addEventListener('message', () => { /* ... */ }によりイベントハンドラーを登録し、送られてきたデータを処理
  2. #postMessage(data)でデータを別スレッドへ送信
                      UI                                      Other
                +------------+               +------------------------------------+
                |    ping!   | +-----------> |addEventListener('message', handler)|
                +------------+               +------------------+-----------------+
                                                                |
                                                                |
                                                                |
                                                                |
                                                                |
                                                                v
+--------------------------------------------+          +-------+-----+
|addEventListenerHandleer('message', handler)| <------+ |     pong!   |
+--------------------------------------------+          +-------------+

制限

Web Worker が動くスレッドでは以下のような制限が掛かります。

  • window
  • document
  • あらゆる DOM 要素

Web Worker ファイルを作る

Worker ファイルは拡張子を.worker.jsとするケースが多いようなのでこのルールに則りworkers/ping-pong.worker.jsというファイルを作ります。このファイルの中にはselfというDedicatedWorkerGlobalScopeがグローバル領域で使えるようになっており、これを通してイベントを扱ったり、データを送信したりします。

以下のコードは送られてきたデータのtypeプロパティ値がpingだった場合pongという文字列データを返す Web Worker です

self.addEventListener('message', event => {
  if (event.data.type === 'ping') {
    self.postMessage('pong'); 
  }
});

Worker インスタンス

スレッドは UI スレッドから Worker コンストラクタを通して建てれます。その時 Worker へは Web Worker となる.jsファイルへのパスを渡します。先程のファイルを使うと以下のようになります。

const worker = new Worker('workers/ping-pong.worker.js');

UI スレッド側でも同じ用にイベントを扱うようにしたり、データを送信します。基本図のように UI スレッドから何かしら処理の起点をあげなければ何も始まりません。

worker.addEventListener('message', event => {
  console.log(event.data);
});

worker.postMessage({type: 'ping'});

もしコードが即実行されるようになっていればリロードする度にpongと表示されるはずです。
また上記のような感じで実装したです。この例はクリックしたら…ですがその度pongとコンソールに表示されます。