音の鳴るブログ

鳴らないこともある

WebWorkerのデバッグ

WebWorker は便利だけどデバッグしにくい。僕は根っからのプリントデバッガーなんだけど、WebWorker は console.log が使えなくて困る。しかし泣いてばかりもいられない。

WebWorker で console.log を使う

WorkerConsole というやり方がある。下記は簡略版だけど、WebWorker側に console というグローバル変数を用意して、log とか error メソッドで呼び出し元に引数を postMessage する。そして、呼び出し元の onmessage でコンソールに表示する。

このやり方には若干問題があって、postMessage を通過出来るものしか表示できない。つまりオブジェクトを表示して DevTools で属性を確認したりとかできない。

// worker.js
global.console = {};
["log", "debug", "info", "error"].forEach(function(method) {
  global.console[method] = function() {
    var args = [].slice.call(arguments).map(function(x) { return x+""; });
    postMessage(["console", method].concat(args));
  };
});

console.log("ウヒョーー!!");
// app.js
window.onmessage = function(e) {
  var msg = e.data || "";
  if (msg[0] === "console") {
    console[msg[1]].apply(console, msg.slice(2));
  }
};

WebWorker のかわりに インラインフレーム を使う

WebWorker は postMessageonmessage を駆使してメッセージのやり取りを行うのだけど、インラインフレームでも同じように postMessageonmessage を使ってメッセージのやり取りを行えるので代用できる。以下の例ではクエリストリング中の "debug" の有無で WebWorker か iframe のどちらかのコンテキストで動作するのだけど recvMsg, sendMsg という2つの関数をつかって app.js と worker.js 間のやり取りを抽象化している。これだと正真正銘本物の console.log が使えるので、オブジェクトを表示して DevTools で属性を確認し放題になる。

// worker.js

var recvMsg, sendMsg;

recvMsg = function(msg) { /* メッセージに応じてなんか処理する */ };

if (typeof window === "undefined") {
  // WebWorker のとき
  onmessage = function(e) {
    recvMsg(e.data);
  };
  sendMsg = function() {
    postMessage([].slice.call(arguments));
  };
  global.console = {};
  ["log", "debug", "info", "error"].forEach(function(method) {
    global.console[method] = function() {
      var args = [].slice.call(arguments).map(function(x) { return x+""; });
      postMessage(["console", method].concat(args));
    };
  });
} else {
  // iframe のとき
  window.onmessage = function(e) {
    recvMsg(e.data);
  };
  sendMsg = function() {
    window.parent.postMessage([].slice.call(arguments), "*")
  };
}
// app.js

var worker, recvMsg, sendMsg;

recvMsg = function(msg) { /* メッセージに応じてなんか処理する */ };

var onmessage = function(e) {
  var msg = e.data || "";
  if (msg[0] === "console") {
    console[msg[1]].apply(console, msg.slice(2));
  } else {
    recvMsg(msg);
  }
};

if (location.search.indexOf("debug") === -1) {
  // WebWorker を使うとき
  worker = new Worker("worker.js");
  worker.onmessage = onmessage;
  sendMsg = function() {
    worker.postMessage([].slice.call(arguments));
  };
} else {
  // ifreme を使うとき
  worker = document.createElement("iframe");
  worker.style.width = worker.style.height = worker.style.borderWidth = 0;
  worker.onload = function() {
    worker.contentWindow.document.write('<script src="worker.js"></script>');
    window.onmessage = onmessage;
    sendMsg = function() {
      worker.contentWindow.postMessage([].slice.call(arguments), "*");
    };
  };
  document.body.appendChild(worker);
}