Web Worker を使用する

by 2 contributors:

Web Workers は、Web コンテンツがスクリプトをバックグラウンドのスレッドで実行するためのシンプルな手段です。Worker スレッドは、ユーザインターフェイスを妨げることなくタスクを実行できます。加えて、それらは (responseXML 属性や channel 属性は常に null ですが) XMLHttpRequest を使用して入出力を行うこともできます。生成された Worker は、生成元が指定したイベントハンドラへメッセージを送ることにより JavaScript コードへメッセージを送ることができます (その逆も可能です)。本記事では、Web Workers の使い方を詳しく紹介します。

Web Workers API

Worker はコンストラクタ (例えば Worker()) を使用して生成されたオブジェクトであり、名前付きの JavaScript ファイル (このファイルは Worker スレッドで実行するコードを持ちます) を実行します。また Worker は、カレントの window とは異なるグローバルコンテキストで実行されます。従って、Worker 内でカレントのグローバルスコープを取得するために (self の代わりに) window ショートカットを使用しても、エラーが返ります。

Worker のコンテキストは、Dedicated Workers (ひとつのスクリプトが利用する、標準的な Workers) の場合は DedicatedWorkerGlobalScope オブジェクトで表します (Shared Workers の場合は SharedWorkerGlobalScope)。Dedicated Worker は、Worker を生成したスクリプトだけがアクセスできます。一方 Shared Worker は、複数のスクリプトからアクセスできます。

注記: Worker のリファレンスドキュメントや追加のガイドについては Web Workers API のトップページをご覧ください。

Worker スレッドでは、いくつかの制限のもとでどのようなコードでも実行できます。例えば、Worker 内から直接 DOM を操作することはできません。また window オブジェクトのデフォルトのメソッドやプロパティで、使用できないものがあります。それでも WebSocketsIndexedDB や Firefox OS 限定の Data Store API といったデータストレージ機構など、window 配下にある多数のアイテムを使用できます。詳しくは Web Workers で使用できる関数やクラス をご覧ください。

メッセージシステムを使用して、Worker とメインスレッドの間でデータを送信します。どちらも postMessage() メソッドを使用してメッセージを送信して、onmessage イベントハンドラによってメッセージに応答します (メッセージは Message イベントの data 属性に収められます)。データは共有せず、コピーします。

Worker は新たな Worker を作成できます。ただし、それらの Worker が親ページとして同じ生成元のもとに存在する場合に限ります。また、Worker はネットワーク I/O のために XMLHttpRequest を使用できますが、例外として XMLHttpRequestresponseXML および channel 属性は常に null を返します。

Dedicated Worker

前述のとおり Dedicated Worker は、呼び出し元のスクリプトだけがアクセスできます。本章では基本的な Dedicated Worker のサンプル JavaScript を見ていきます (Dedicated Worker を実行する)。このサンプルでは、乗算を行う 2 つの数値を入力できます。数値は Dedicated Worker に送られて乗算され、そしてページに返された計算結果を表示します。

これはあまり面白みのないサンプルですが、基本的な Worker のコンセプトを紹介する間はシンプルに保とうと考えています。より高度な詳細情報は、本章の後半で扱います。

Worker 機能を検出する

エラー制御と後方互換性を向上させるため、以下のように Worker へアクセスするコードを包み込むとよいでしょう (main.js):

if (window.Worker) {

  ...

}

Dedicated Worker を生成する

新しい Worker は簡単に生成できます。必要なことは、Worker スレッドで実行するスクリプトの URI を指定した Worker() コンストラクタを呼び出すだけです (main.js):

var myWorker = new Worker("worker.js");

Dedicated Worker とメッセージをやりとりする

Worker のマジックは、postMessage() メソッドと onmessage イベントハンドラによって実現します。Worker にメッセージを送りたいときは、以下のようにしてメッセージを送信します (main.js):

first.onchange = function() {
  myWorker.postMessage([first.value,second.value]);
  console.log('Message posted to worker');
}

second.onchange = function() {
  myWorker.postMessage([first.value,second.value]);
  console.log('Message posted to worker');
}

変数 first および second で示される、2 つの <input> 要素があります。どちらかの値が変わると、双方の値を配列として Worker へ送信するために myWorker.postMessage([first.value,second.value]) を使用します。メッセージでは、おおむねどのようなものでも送信できます。

Worker では以下のようにイベントハンドラのブロックにコードを記述すると、メッセージを受け取ったときに応答できます (worker.js):

onmessage = function(e) {
  console.log('Message received from main script');
  var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
  console.log('Posting message back to main script');
  postMessage(workerResult);
}

onmessage ハンドラにより、メッセージを受け取ったときになんらかののコードを実行できます。メッセージ自体は、message イベントの data 属性で手に入ります。サンプルコードでは2つの数値で乗算を行った後、再び postMessage() を使用して計算結果をメインスレッドに返しています。

メインスレッドに戻ると再び onmessage を使用して、Worker から返されたメッセージに応答します:

myWorker.onmessage = function(e) {
  result.textContent = e.data;
  console.log('Message received from worker');
}

ここではメッセージイベントからデータを取り出して、result の textContent へ格納しています。よって、ユーザは計算結果を参照できます。

注記: Worker コンストラクタの引数として渡す URI は、同一生成元ポリシーに従わなければなりません。

現在は、どの URI を同一生成元とするかはブラウザベンダーにより異なります。Gecko 10.0 (Firefox 10.0 / Thunderbird 10.0 / SeaMonkey 2.7) 以降は data URI を許可しますが、Internet Explorer 10 は Blob URI を Worker 向けのスクリプトとして認めません。

注記: メインのスクリプトスレッドで onmessage および postMessage() を使用するときは Worker オブジェクトに付随させなければなりませんが、Worker 内ではそのようにする必要はありません。これは、Worker 内ではそれ自身が実質的にグローバルスコープであるためです。
注記: メッセージをメインスレッドと Worker の間でやりとりするとき、メッセージは共有せずに "伝送" (移動) します。詳しい解説は、Transferring_data_to_and_from_workers.3A_further_detailsをご覧ください。

Worker を終了する

実行中の Worker を直ちに終了したい場合は、Worker の terminate() メソッドを呼び出してください:

myWorker.terminate();

Worker スレッドは自身の操作を完了したり自身をクリーンアップしたりすることなく、直ちに終了します。

Worker は自身の close メソッドを呼び出すことで、自分自身を終了できます:

close();

エラーハンドリング

Worker で実行時エラーが発生すると、onerror イベントハンドラが呼び出されます。これは、ErrorEvent インターフェイスを実装している error イベントを受け取ります。

イベントは伝播せず、またキャンセルすることができます。発生元のデフォルトアクションを抑制するため、Worker はエラーイベントの preventDefault() メソッドを呼び出すことができます。

エラーイベントには、以下の重要な 3 つの項目があります:

message
人間が読み取れるエラーメッセージです。
filename
エラーが発生したスクリプトのファイル名です。
lineno
スクリプトファイル内でエラーが発生した場所の行番号です。

サブ Worker を生成する

必要であれば、Worker がさらに Worker を生成することができます。いわゆるサブ Worker は、親ページと同じ生成元で提供しなければなりません。またサブ Worker 用の URI は、その所有ページではなく親 Worker の場所からの相対位置として分析されます。これは、Worker の依存関係を追跡し続けることを容易にします。

スクリプトやライブラリをインポートする

Worker スレッドはグローバル関数や、同一ドメインのスクリプトを自身のスコープへインポートするための importScripts() にアクセスできます。これはインポートするリソースの URI を 0 個以上、引数として受け入れます。以下のサンプルはすべて有効です:

importScripts();                        /* 何もインポートしない */
importScripts('foo.js');                /* "foo.js" をインポート */
importScripts('foo.js', 'bar.js');      /* 2 つのスクリプトをインポート */

ブラウザはそれぞれのスクリプトを読み込み、実行します。Worker は各スクリプトのグローバルオブジェクトを使用できます。スクリプトを読み込むことができない場合は NETWORK_ERROR を発生させて、それ以降のコードを実行しません。それでも、すでに実行されたコード (window.setTimeout() で繰り延べされているコードを含みます) は動作します。importScript() メソッドより後方にある関数の宣言は、常にコードの残りの部分より先に評価されることから、同様に保持されます。

注記: スクリプトは順不同にダウンロードされますが、実行は importScripts() に渡したファイル名の順に行います。これは同期的に行われます。すべてのスクリプトの読み込みと実行が行われるまで importScripts() から戻りません。

Shared Worker

Shared Worker は、生成元が同一であれば (異なる window、iframe、Worker からであっても) 複数のスクリプトからアクセスできます。本章では基本的な Shared Worker のサンプル JavaScript を見ていきます (Shared Worker を実行する)。こちらは Dedicated Worker のサンプルと似ていますが、2 つの数値で乗算を行うスクリプトと数値を 2 乗するスクリプトという、別々のスクリプトファイルが扱う 2 つの関数を使用できる点が異なります。どちらのスクリプトも同じ Worker を使用して、実際に必要な計算を行います。

ここでは、Dedicated Worker と Shared Worker の違いについて注目します。このサンプルでは 2 つの HTML ページがあり、それぞれの JavaScript は同じひとつの Worker ファイルを使用するようになっています。

Shared Worker を生成する

Worker の生成方法は Dedicated Worker の場合とほとんど同じですが、コンストラクタ名が異なります (index.html および index2.html をご覧ください)。それぞれのページで、以下のようなコードを使用して Worker を立ち上げなければなりません:

var myWorker = new SharedWorker("worker.js");

Shared Worker の大きな違いのひとつが、port オブジェクトを通して通信しなければならないことです。スクリプトが Worker と通信するために使用できる、明示的なポートが開きます (これは、Dedicated Worker でも暗黙的に開いています)。

ポートへの接続は onmessage イベントハンドラを使用して暗黙的に、あるいはメッセージを送信する前に start() メソッドを使用して明示的に開始しなければなりません。デモの multiply.js および worker.js ファイルでは start() メソッドを呼び出していますが、onmessage イベントハンドラも使用していますので必須ではありません。start() の呼び出しは、addEventListener() メソッドで message イベントを拾い上げる場合にのみ必要です。

ポート接続を開始するために start() メソッドを使用するとき、双方向の通信が必要である場合は親スレッドと Worker の両方で呼び出さなければなりません。

myWorker.port.start();  // 親スレッドで呼び出す
port.start();  // Worker スレッドで呼び出す。変数 port はポートを参照していると仮定

Shared Worker とメッセージをやりとりする

前述のとおり Worker にメッセージを送信できるようになりましたが、postMessage() メソッドは port オブジェクトを通して呼び出さなければなりません (繰り返しますが、同様の構造が multiply.js および square.js に存在します):

squareNumber.onchange = function() {
  myWorker.port.postMessage([squareNumber.value,squareNumber.value]);
  console.log('Message posted to worker');
}

Worker に移ります。こちらは若干複雑さが増しています (worker.js):

onconnect = function(e) {
  var port = e.ports[0];
  port.onmessage = function(e) {
    var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
    port.postMessage(workerResult);
  }
  port.start();  // onmessage イベントを使用していますので必須ではありません
}

始めに、ポートへの接続が発生したとき (すなわち、親スレッドで onmessage イベントをセットアップしたときや親スレッドで start() メソッドを明示的に呼び出したとき) にコードを実行するため onconnect ハンドラを使用します。

イベントオブジェクトの ports 属性を使用してポートを取り出し、変数に格納します。

次に、計算を実行して結果をメインスレッドに返すため、ポートの onmessage ハンドラを使用します。Worker スレッドで onmessage ハンドラをセットアップすると、親スレッドに戻すポート接続を暗黙的に開きます。従って、実際は前述のとおり port.start() を呼び出す必要はありません。

最後に、メインスレッドに戻ってメッセージを扱います (繰り返しますが、同様の構造が multiply.js および square.js に存在します):

myWorker.port.onmessage = function(e) {
  result2.textContent = e.data[0];
  console.log('Message received from worker');
}

ポートを通して Worker からメッセージが戻ったときは、結果のデータ型を確認してから適切なパラグラフに計算結果を挿入します。

スレッドセーフについて

Worker インターフェイスは実際の OS レベルのスレッドを生成しますので注意深いプログラマは、気をつけなければ同時実行によって、コードにおいて "興味深い" 効果を引き起こす可能性があると懸念するかもしれません。

しかし Web Workers は他のスレッドとの通信ポイントが注意深く制御されていますので、実際は同時実行による問題を引き起こすことが非常に困難です。スレッドセーフではないコンポーネントや DOM にはアクセスできません。また、スレッド内外のデータはシリアライズしたオブジェクトを通して渡さなければなりません。よって、コードで問題を起こすことはとても難しいのです。

Worker とのデータ伝送の詳細

メインページと Worker との間で送られるメッセージは、共有されるのではなくコピーされます。オブジェクトは Worker に渡している間シリアライズされ、その後戻されたときにシリアライズが解除されます。ページと Worker は同一のインスタンスを共有しませんので、結果として双方に複製が作られます。ほとんどのブラウザはこの機能を structured cloning として実装しています。

これを説明するため、 worker からメインページおよびその逆の移動において共有されず複製される値の動作を市ミューレションする、教育的な用途の関数 emulateMessage() を作成しましょう:

function emulateMessage (vVal) {
    return eval("(" + JSON.stringify(vVal) + ")");
}

// テスト

// テスト #1
var example1 = new Number(3);
alert(typeof example1); // object
alert(typeof emulateMessage(example1)); // number

// テスト #2
var example2 = true;
alert(typeof example2); // boolean
alert(typeof emulateMessage(example2)); // boolean

// テスト #3
var example3 = new String("Hello World");
alert(typeof example3); // object
alert(typeof emulateMessage(example3)); // string

// テスト #4
var example4 = {
    "name": "John Smith",
    "age": 43
};
alert(typeof example4); // object
alert(typeof emulateMessage(example4)); // object

// テスト #5
function Animal (sType, nAge) {
    this.type = sType;
    this.age = nAge;
}
var example5 = new Animal("Cat", 3);
alert(example5.constructor); // Animal
alert(emulateMessage(example5).constructor); // Object

共有されず複製される値をメッセージと呼びます。おわかりかと思いますが メッセージpostMessage() を使用してメインスレッドへ、またはメインスレッドから送信でき、また message イベントの data 属性に、Worker から返されたデータが含まれます。

example.html: (メインページ):

var myWorker = new Worker("my_task.js");

myWorker.onmessage = function (oEvent) {
  console.log("Worker said : " + oEvent.data);
};

myWorker.postMessage("ali");

my_task.js (worker):

postMessage("I\'m working before postMessage(\'ali\').");

onmessage = function (oEvent) {
  postMessage("Hi " + oEvent.data);
};

structured cloning アルゴリズムは JSON や、循環参照など JSON ではできないいくつかのものを受け入れることができます。

データ引き渡しのサンプル

例 #1: 汎用的な "非同期 eval()" を生成する

以下のサンプルでは、Worker 内で実行可能な JavaScript コードを非同期的に実行するために、Worker を使用する方法を示します。なお、Worker 内に eval() があります:

// 構文: asyncEval(code[, listener])

var asyncEval = (function () {

  var aListeners = [], oParser = new Worker("data:text/javascript;charset=US-ASCII,onmessage%20%3D%20function%20%28oEvent%29%20%7B%0A%09postMessage%28%7B%0A%09%09%22id%22%3A%20oEvent.data.id%2C%0A%09%09%22evaluated%22%3A%20eval%28oEvent.data.code%29%0A%09%7D%29%3B%0A%7D");

  oParser.onmessage = function (oEvent) {
    if (aListeners[oEvent.data.id]) { aListeners[oEvent.data.id](oEvent.data.evaluated); }
    delete aListeners[oEvent.data.id];
  };


  return function (sCode, fListener) {
    aListeners.push(fListener || null);
    oParser.postMessage({
      "id": aListeners.length - 1,
      "code": sCode
    });
  };

})();

data URL はネットワークリクエストと等価であり、以下の応答を返します:

onmessage = function (oEvent) {
	postMessage({
		"id": oEvent.data.id,
		"evaluated": eval(oEvent.data.code)
	});
}

使用例:

// 非同期の alert メッセージ...
asyncEval("3 + 2", function (sMessage) {
    alert("3 + 2 = " + sMessage);
});

// 非同期のメッセージ出力...
asyncEval("\"Hello World!!!\"", function (sHTML) {
    document.body.appendChild(document.createTextNode(sHTML));
});

// 非同期の void...
asyncEval("(function () {\n\tvar oReq = new XMLHttpRequest();\n\toReq.open(\"get\", \"http://www.mozilla.org/\", false);\n\toReq.send(null);\n\treturn oReq.responseText;\n})()");

例 #2: 高度な JSON データの引き渡しと振り分けシステムを作成する

複雑なデータを渡さなければならず、またメインページと Worker の双方で多くのさまざまな関数を呼び出さなければならない場合は、以下のようなシステムを作成するとよいでしょう。

example.html (メインページ):

<!doctype html>
<html>
<head>
<meta charset="UTF-8"  />
<title>MDN Example - Queryable worker</title>
<script type="text/javascript">
  /*
    QueryableWorker インスタンスのメソッド:
     * sendQuery(照会可能な関数名, 渡す引数 1, 渡す引数 2, 等々): Worker で照会可能な関数を呼び出す
     * postMessage(文字列または JSON データ): Worker.prototype.postMessage() を参照
     * terminate(): Worker を終了
     * addListener(名称, 関数): リスナを追加
     * removeListener(名称): リスナを削除
    QueryableWorker インスタンスのプロパティ:
     * defaultListener: Worker が postMessage() 関数を直接呼び出したときに限り実行される、デフォルトのリスナ
  */
  function QueryableWorker (sURL, fDefListener, fOnError) {
    var oInstance = this, oWorker = new Worker(sURL), oListeners = {};
    this.defaultListener = fDefListener || function () {};
    oWorker.onmessage = function (oEvent) {
      if (oEvent.data instanceof Object && oEvent.data.hasOwnProperty("vo42t30") && oEvent.data.hasOwnProperty("rnb93qh")) {
        oListeners[oEvent.data.vo42t30].apply(oInstance, oEvent.data.rnb93qh);
      } else {
        this.defaultListener.call(oInstance, oEvent.data);
      }
    };
    if (fOnError) { oWorker.onerror = fOnError; }
    this.sendQuery = function (/* 照会可能な関数名, 渡す引数 1, 渡す引数 2, 等々 */) {
      if (arguments.length < 1) { throw new TypeError("QueryableWorker.sendQuery - not enough arguments"); return; }
      oWorker.postMessage({ "bk4e1h0": arguments[0], "ktp3fm1": Array.prototype.slice.call(arguments, 1) });
    };
    this.postMessage = function (vMsg) {
      // call() メソッドをを使用する必要性はないと考えます
      // oWorker.postMessage(vMsg); を使用するのはどうか?
      // 正常に完了することは同じですが、
      // プロトタイプチェーンの探索がないため少しだけ速くなります
      Worker.prototype.postMessage.call(oWorker, vMsg);
    };
    this.terminate = function () {
      Worker.prototype.terminate.call(oWorker);
    };
    this.addListener = function (sName, fListener) {
      oListeners[sName] = fListener;
    };
    this.removeListener = function (sName) {
      delete oListeners[sName];
    };
  };

  // 自製の "照会可能な" worker
  var oMyTask = new QueryableWorker("my_task.js" /* , 既定のメッセージリスナ [省略可], エラーリスナ [省略可] */);

  // 自製の "リスナ"
  oMyTask.addListener("printSomething", function (nResult) {
    document.getElementById("firstLink").parentNode.appendChild(document.createTextNode(" The difference is " + nResult + "!"));
  });

  oMyTask.addListener("alertSomething", function (nDeltaT, sUnit) {
    alert("Worker waited for " + nDeltaT + " " + sUnit + " :-)");
  });
</script>
</head>
<body>
  <ul>
    <li><a id="firstLink" href="javascript:oMyTask.sendQuery('getDifference', 5, 3);">What is the difference between 5 and 3?</a></li>
    <li><a href="javascript:oMyTask.sendQuery('waitSomething');">Wait 3 seconds</a></li>
    <li><a href="javascript:oMyTask.terminate();">terminate() the Worker</a></li>
  </ul>
</body>
</html>

my_task.js (worker):

// 独自の PRIVATE 関数

function myPrivateFunc1 () {
  // 何らかの処理
}

function myPrivateFunc2 () {
  // 何らかの処理
}

// 等々...

// 独自の PUBLIC 関数 (つまり、メインページから照会可能)

var queryableFunctions = {
  // 例 1: 2 つの値の差を得る:
  getDifference: function (nMinuend, nSubtrahend) {
      reply("printSomething", nMinuend - nSubtrahend);
  },
  // 例 2: 3 秒待つ
  waitSomething: function () {
      setTimeout(function() { reply("alertSomething", 3, "seconds"); }, 3000);
  }
};

// システム関数

function defaultQuery (vMsg) {
  // メインページが queryableWorker.postMessage() メソッドを直接呼び出したときに限り実行される、デフォルトの PUBLIC 関数
  // 何らかの処理
}

function reply (/* リスナ名, 渡す引数 1, 渡す引数 2, 等々 */) {
  if (arguments.length < 1) { throw new TypeError("reply - not enough arguments"); return; }
  postMessage({ "vo42t30": arguments[0], "rnb93qh": Array.prototype.slice.call(arguments, 1) });
}

onmessage = function (oEvent) {
  if (oEvent.data instanceof Object && oEvent.data.hasOwnProperty("bk4e1h0") && oEvent.data.hasOwnProperty("ktp3fm1")) {
    queryableFunctions[oEvent.data.bk4e1h0].apply(self, oEvent.data.ktp3fm1);
  } else {
    defaultQuery(oEvent.data);
  }
};

それぞれのメインページ -> Worker および Worker -> メインページのメッセージの、コンテンツを切り替えることができます。

所有権の譲渡 (Transferable Objects) によるデータの引き渡し

Google Chrome 17 以降および Firefox 18 以降には、特定の種類のオブジェクト (Transferable インターフェイスを実装するオブジェクトである Transferable Object) を高いパフォーマンスで Worker から、または Worker へ渡すための、追加の手段があります。Transferable Object はあるコンテキストから別のコンテキストへ、コピー操作なしに転送されます。これにより、大量のデータセットを送信する際のパフォーマンスが大きく向上します。C/C++ の経験者であれば、参照渡しと考えてください。しかし参照渡しとは異なり、転送されると元のコンテキストからその版が失われます。そしてその所有権は新しいコンテキストに譲渡されます。例えば ArrayBuffer をメインアプリから Worker スクリプトへ転送するとき、元の ArrayBuffer はクリアされて使用できなくなります。その内容物は (文字どおり) Worker コンテキストに転送 (transfer)されます。

// 32MB の "file" を作成して埋めます。
var uInt8Array = new Uint8Array(1024*1024*32); // 32MB
for (var i = 0; i < uInt8Array.length; ++i) {
  uInt8Array[i] = i;
}

worker.postMessage(uInt8Array.buffer, [uInt8Array.buffer]);

注記: Transferable Object、パフォーマンス、およびメソッドの機能検出について詳しくは、HTML5 Rocks の Transferable Objects: Lightning Fast! をご覧ください。

埋め込み Worker

通常のスクリプトを <script> 要素で埋め込むように、Worker のコードを Web ページに埋め込むための "公式な" 方法はありません。しかし src 属性を持たず、また実行可能な MIME タイプを示さない type 属性を持つ <script> 要素は、JavaScript が使用できるデータブロック要素であると判断されます。"データブロック" はほとんどのテキストデータを持つことができる、HTML5 の一般的な機能です。よって、以下の方法で Worker を埋め込むことができます:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>MDN Example - Embedded worker</title>
<script type="text/js-worker">
  // MIME タイプが text/js-worker であるため、このスクリプトは JS エンジンに解釈されません。
  var myVar = "Hello World!";
  // worker の残りのコードをここに置きます。
</script>
<script type="text/javascript">
  // MIME タイプが text/javascript であるため、このスクリプトは JS エンジンに解釈されます。
  function pageLog (sMsg) {
    // fragment を使用: ブラウザは 1 回だけレンダリング/リフローします。
    var oFragm = document.createDocumentFragment();
    oFragm.appendChild(document.createTextNode(sMsg));
    oFragm.appendChild(document.createElement("br"));
    document.querySelector("#logDisplay").appendChild(oFragm);
  }
</script>
<script type="text/js-worker">
  // MIME タイプが text/js-worker であるため、このスクリプトは JS エンジンに解釈されません。
  onmessage = function (oEvent) {
    postMessage(myVar);
  };
  // worker の残りのコードをここに置きます。
</script>
<script type="text/javascript">
  // MIME タイプが text/javascript であるため、このスクリプトは JS エンジンに解釈されます。

  // 
  // 以前は blob を構築していましたが、
  // 現在は Blob を使用します:
  var blob = new Blob(Array.prototype.map.call(document.querySelectorAll("script[type=\"text\/js-worker\"]"), function (oScript) { return oScript.textContent; }),{type: "text/javascript"});

  // すべての "text/js-worker" スクリプトを含む、新たな document.worker プロパティを生成します。
  document.worker = new Worker(window.URL.createObjectURL(blob));

  document.worker.onmessage = function (oEvent) {
    pageLog("Received: " + oEvent.data);
  };

  // worker を開始します。
  window.onload = function() { document.worker.postMessage(""); };
</script>
</head>
<body><div id="logDisplay"></div></body>
</html>

埋め込み Workerが、新たな自製の document.worker プロパティの入れ子になります。

追加サンプル

ここでは Web Worker の使用方法について、さらにサンプルを示します。

バックグラウンドで演算を行う

Worker は主に、ユーザインターフェイスのスレッドを妨げずに CPU 負荷が大きい演算を実行するために役立ちます。このサンプルでは、Worker をフィボナッチ数の計算に使用します。

JavaScript コード

以下の JavaScript コードはファイル "fibonacci.js" に保存され、次章の HTML が参照します。

var results = [];

function resultReceiver(event) {
  results.push(parseInt(event.data));
  if (results.length == 2) {
    postMessage(results[0] + results[1]);
  }
}

function errorReceiver(event) {
  throw event.data;
}

onmessage = function(event) {
  var n = parseInt(event.data);

  if (n == 0 || n == 1) {
    postMessage(n);
    return;
  }

  for (var i = 1; i <= 2; i++) {
    var worker = new Worker("fibonacci.js");
    worker.onmessage = resultReceiver;
    worker.onerror = errorReceiver;
    worker.postMessage(n - i);
  }
 };

Worker は、Worker オブジェクトの postMessage() が呼び出されたときにメッセージを受け取る関数を onmessage プロパティに設定します (これは onmessage という名前のグローバル変数関数を定義することとは違いますので注意してください。var onmessagefunction onmessage はその名前でグローバルプロパティを定義しますが、Worker を生成した Web ページが送信したメッセージを受け取る関数を登録しません)。これは再帰的に開始して、計算の反復処理を行うために自身のコピーを生成します。

HTML コード

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8"  />
    <title>Test threads fibonacci</title>
  </head>
  <body>

  <div id="result"></div>

  <script language="javascript">

    var worker = new Worker("fibonacci.js");

    worker.onmessage = function(event) {
      document.getElementById("result").textContent = event.data;
      dump("Got: " + event.data + "\n");
    };

    worker.onerror = function(error) {
      dump("Worker error: " + error.message + "\n");
      throw error;
    };

    worker.postMessage("5");

  </script>
  </body>
</html>

Web ページは ID result を持つ div 要素を作成して、結果を表示するために使用します。そして、Worker を生成します。Worker を生成した後は、onmessage ハンドラを div 要素の内容物を指定することで結果を表示するように、また onerror ハンドラはエラーメッセージをダンプするように設定します。

最後に、Worker を開始するためにメッセージを送信します。

このサンプルを試してみてください

バックグラウンドで Web 入出力を行う

このサンプルは、Using workers in extensions の記事に掲載しています。

複数の Worker にタスクを分割する

マルチコアのコンピュータが次第に一般化することで複数の Worker に複雑な計算処理を分割することが有用になり、それらのタスクを複数の CPU コアで実行することが可能になります。

その他の Worker

Dedicated Worker や Shared Worker に加えて、他の種類の Worker を使用できます:

  • Service Worker は本質的に、Web アプリケーション、ブラウザ、ネットワークの間 (可能であれば) でプロキシサーバーとして振る舞います。これは、例えば効果的なオフライン体験、ネットワークリクエストへの介入、ネットワークが利用可能かやサーバ上で更新された資源に応じて適切なアクションを行うなどを意図しています。また、プッシュ通知やバックグラウンド同期 API へのアクセスも可能です。
  • Chrome Worker は、アドオンを開発していて拡張機能で Worker の使用を望んでおり、また Worker で js-ctypes にアクセスする場合に使用できる、Firefox 限定の Worker です。詳しくは ChromeWorker をご覧ください。
  • Audio Worker は、Web Worker のコンテキスト内でスクリプトによるオーディオ処理を直接実行する機能を提供します。

Worker で使用できる関数とインターフェイス

以下を含む、ほとんどの標準的な JavaScript 機能を Web Worker 内で使用できます:

Worker で実行できないことは主に、親ページに直接影響を与えるものです。これは、DOM の操作やページのオブジェクトを使用することを含みます。DedicatedWorkerGlobalScope.postMessage を使用してメインスクリプトにメッセージを戻してから変更操作を行う形で、間接的に実行しなければなりません。

注記: Worker で使用できる関数の完全なリストは、Worker で使用できる関数とインターフェイスでご覧ください。

仕様

仕様書 策定状況 コメント
WHATWG HTML Living Standard Living Standard Web Workers から変更なし
Web Workers 勧告候補 初期の定義

ブラウザ実装状況

機能 Chrome Firefox (Gecko) Internet Explorer Opera Safari (WebKit)
基本サポート 4 Unknown (3.5) 10.0 10.6 4
Shared Worker 4 29 (29) 未サポート 10.6 4
structured cloning によるデータ渡し 13 8 (8) 10.0 11.5 6
transferable objects によるデータ渡し 17 webkit
21
18 (18) 未サポート 15 6
グローバル URL 10 (webkitURL として)
23
21 (21) 11 15 6 (webkitURL として)
機能 Android Chrome Mobile Firefox Mobile (Gecko) Firefox OS (Gecko) IE Phone Opera Mobile Safari Mobile
基本サポート 4.4 4 3.5 1.0.1 10.0 11.5 5.1
Shared Worker 4 8 1.0.1 未サポート
structured cloning によるデータ渡し 4 8 1.0.1 未サポート
transferable objects によるデータ渡し 18 1.0.1 未サポート

ブラウザごとの注記

  • Chrome/Opera は、Worker をローカルで実行しようとすると "Uncaught SecurityError: Failed to construct 'Worker': Script at 'file:///Path/to/worker.js' cannot be accessed from origin 'null'." というエラーが発生します。適切なドメインであることが必要です。
  • Safari 7.1.2 の時点では Worker 内で console.log を呼び出すことができますが、コンソールには何も表示されません。以前のバージョンの Safari では、Worker 内で console.log を呼び出すことができませんでした。

関連情報

ドキュメントのタグと貢献者

Contributors to this page: yyss, ethertank
最終更新者: yyss,