WebWorkerを使ってみる。

最近のJavascript関係ではWebSocketとWebWorkerが気になってたんだけど、
WebSocketの方はnode.jsとかJettyとか?サーバ側にも仕掛けが必要なので、
手っ取り早くできる方ってことで、今更ながらWebWorkerを試してみた。


試してみたかったことは次の点。

  • 基本的な使い方
  • jQueryとかのライブラリが使えるか
  • オレオレクラスが使えるか

とりあえずこれらに絞ってお試し。

基本的な使い方


インスタンスを生成して、メッセージでやりとりする。
まず、こんな感じのHTMLを用意して。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>Web Worker Test</title>
    </head>
    <body>
        <h1>Web Worker Test</h1>
        <script type="text/javascript">
        // Workerのインスタンスを生成する
        var w = new Worker('test.js');
        // messageイベントのリスナーを追加
        w.addEventListener('message', onMessage, false);
        // イベントリスナー
        function onMessage(e) {
            // イベントデータのログ書きだし
            console.log(e.data);
        }
        // 文字列を送信 
        w.postMessage('Test');
        // 数値を送信
        w.postMessage(12345);
        // 真偽値を送信
        w.postMessage(false);
        // 配列を送信
        w.postMessage([1, 2, '3']);
        // オブジェクトを送信
        w.postMessage({ x : 100, y : 200 });
        </script>
    </body>
</html>

Workerで使うJavascriptファイルを用意する。

// messageイベントのリスナー追加
addEventListener('message', onMessage, false);

// イベントリスナー
function onMessage(e) {
    // データ取りだし
    var msg = e.data;
    // 受けとったデータをメッセージ送信
    postMessage('recive : [' + JSON.stringify(msg) + ':' + typeof(msg) + ']');
}

で、HTMLをブラウザで開くと、得られる結果はこんな感じ。

recive : ["Test":string]
recive : [12345:number]
recive : [false:boolean]
recive : [[1,2,"3"]:object]
recive : [{"x":100,"y":200}:object]

Workerのインスタンス生成側とWorker内でメッセージがやりとりされてる。
スコープが異なるからリスナーのonMessage関数は名前だぶってても上書きされない。
Worker内ではconsole.log使えなかった。

jQueryとかのライブラリが使えるか


Workerのスコープ内ではDOMに手出しができないので、
セレクタとかは使えないけど、$.ajaxくらいは使えるかテスト。

Workerを使うHTMLを新たに用意。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>Web Worker Test</title>
    </head>
    <body>
        <h1>Web Worker Test</h1>
        <script type="text/javascript">
        // Workerのインスタンスを生成する
        var w = new Worker('test.js');
        // messageイベントのリスナーを追加
        w.addEventListener('message', onMessage, false);
        // イベントリスナー
        function onMessage(e) {
            // イベントデータのログ書きだし
            console.log(e.data);
        }
        // メッセージを送信 
        w.postMessage('get data');
        </script>
    </body>
</html>

Worker用のJavascriptファイルを用意。

// 外部ファイル読み込み
importScripts('jquery-1.4.3.min.js');

// messageイベントのリスナー追加
addEventListener('message', onMessage, false);

// イベントリスナー
function onMessage(e) {
    $.ajax({
        url : './test.txt', // 中身はHelloだけのテキスト
        success : function(data) {
            postMessage(data);
        }
    });
}

で、実行してみると、Firebugのエラーメッセージ。

window is not defined
http://localhost/exam/worker/jquery-1.4.3.min.js
Line 166

ChromiumのDevelopツールのエラーメッセージ。

test.js:1Uncaught ReferenceError: window is not defined

windowなんてないよ!って怒られる。windowにもdocumentにも手をだせないから、
ライブラリ内でふれちゃってるところがあると、やっぱりだめ。

jQueryに頼らずにAjax通信やるとこんな感じ。

// messageイベントのリスナー追加
addEventListener('message', onMessage, false);

// イベントリスナー
function onMessage(e) {
    var xhr = new XMLHttpRequest();
    // 同期モードで送信 
    xhr.open('GET', './test.txt', false);
    xhr.onreadystatechange = function() {
        if(xhr.readyState === 4) {
            // 受信できたらメッセージを送信
            var res = xhr.responseText;
            postMessage(res);
        }
    };
    xhr.send(null);
}

Worker内なので、同期モードでリクエスト投げてもWorkerの外は影響受けない。

オレオレクラスが使えるか


最後、自作クラスが使えるか確認。
まずはWorkerのインスタンス生成側。

window.onload = function() {
    // Workerのインスタンス生成(キャッシュきかないようにちょっと対策)
    var w = new Worker('worker.js?' + (new Date()).getTime());
    // メッセージイベントのリスナー追加
    w.addEventListener('message', onMessage, false);
    for(var i = 0; i < 10; i++) {
        // データを10回通知
        w.postMessage({ cmd : 'set', data : 'value' + (i + 1) });
    }
    // 送ったデータで作った文字列を返すように通知
    w.postMessage({ cmd : 'get' });
};

// メッセージイベントリスナー
function onMessage(e) {
    // コマンド
    var cmd = e.data.cmd;
    if(cmd === 'get') {
        // ログにデータ書きだし
        console.log(e.data.data);        
    }
}

Worker側のスクリプト

// 外部スクリプト読み込み
importScripts('StringBuilder.js');

// メッセージイベントのリスナー追加
addEventListener('message', onMessage, false);

// インスタンス生成
var sb = new StringBuilder();

// メッセージイベントリスナー
function onMessage(e) {
    // コマンド
    var cmd = e.data.cmd;
    // データ
    var data = e.data.data || null;
    switch(cmd) {
        case 'set' :
            // 追加
            sb.add(data);
            break;
        case 'get' :
            // 文字列にして返す
            postMessage({ cmd : 'get', data : sb.toString() });
            break;
        default :
            // コマンドなかったらエラー
            throw new Error('Operation Not Found. [' + cmd +']');
            break;
    }
}

外部読み込みするクラスファイル

/**
 * 文字列連結クラス
 */
function StringBuilder() {
    this.init.apply(this, arguments);
}
StringBuilder.prototype = {
    /**
     * コンストラクタ
     */
    init : function() {
        // 初期化
        this._seq = [];
    },
    /**
     * 値を追加します
     *
     * @param {String|Number|Boolean} s 追加値
     */
    add : function(s) {
        // 追加
        this._seq[this._seq.length] = s;
    },
    /**
     * 文字列として取得します
     *
     * @return {String} 文字列
     */
    toString : function() {
        // 連結
        return this._seq.join('');
    }
};

実行結果は「value1value2value3value4value5value6value7value8value9value10」が問題なく得られる。
今回のはクラスにするまでもない&Worker使うまでもない処理だけど、
windowとかDOMとかに手をつけてなければ、過去の資産も流用できるっぽい。


やってたらまた調べたいこと増えたので、もうちょっと遊んでみるつもり。