SitePoint: New Articles, Fresh Thinking for Web Developers and Designers

SitePointのJavaScript Timer-Based Pseudo-ThreadingにおいてJavaScriptで擬似マルチスレッドプログラミングを実装する基本的な方法が紹介されている。アイディアの根幹は細切れにした関数をsetTimeout()で一定期間区切りにして、順次実行していくというもの。実際のところ、このテクニックを使って擬似マルチスレッドプログラミングを実施するのは開発の手間を考えるとあまり効率のいい方法とはいいにくい。効率のいいマルチスレッド処理を実現するにはWeb Workersを使うというのが現実的といえる。しかし、掲載されているサンプルコードが興味深く、Web Workersが利用できないブラウザ向けの手法としてこうした方法を知っておくというのは意味がある。

JavaScript Timer-Based Pseudo-Threadingはいくつかの点でプログラミング上の参考になる。主な注目ポイントは次のとおり。

  • setTimeout()を使った擬似スレッドを実現する方法が紹介されている。
  • setTimeout()の動作特性を理解しやすい。
  • 関数の配列を処理する方法として「func.shift()();」という表記を使っている。
  • arguments.calleeを使うことで無名関数の再帰処理を利用している。

JavaScript Timer-Based Pseudo-Threadingに掲載されているサンプルを参考にして、そのままブラウザで利用してわかるように若干の変更を加えたHTMLと、その実行結果を次に掲載する。

<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>タイマーベース擬似スレッド 基本アイディア</title>
</head>
<body>
  <h1 id="message"></h1>
  <script type="text/javascript">
  msg = document.getElementById("message");
  function f1() {
    for (i=0; i<10;i++) {
      msg.innerHTML = msg.innerHTML + "f1("+i+") ";
    }
  }
  function f2() {
    for (i=0; i<10;i++) {
      msg.innerHTML = msg.innerHTML + "f2("+i+") ";
    }
  }
  function f3() {
    for (i=0; i<10;i++) {
      msg.innerHTML = msg.innerHTML + "f3("+i+") "
    };
  }
  function ProcessThread(func) {
    setTimeout(function() {
      func.shift()();
      if (func) {
        setTimeout(arguments.callee, 1000);
      }
    }, 1000);
  }
  ProcessThread([f1, f2, f3]);
  msg.innerHTML = msg.innerHTML + "finish ";
  </script>
</body>
</html>

setTimeout()は指定したミリ秒待ってから指定された関数を実行するため、スクリプトの最後の行が先に実行されることがわかる

次にf1()が実行されている

次にf2()が実行される

最後にf3()が実行される

基本となるコードは「setTimeout(関数, ミリ秒)」というもの。指定したミリ秒後に指定した関数を実行するというもの。f1()の処理よりも先に「 msg.innerHTML = msg.innerHTML + "finish ";」の処理が走るところからも、setTimeout()の動きがわかる。

ひたすら長い時間がかかる関数処理をいくつかのブロックに分割してsetTimeoutを使って順次実行するようにすれば、ブラウザによるスクリプト停止を避けることができる。また分割した処理を順番に実行すれば、擬似的にマルチスレッドのように同時に処理を進めているように見せかけることもできる、というわけだ。

注目したいのは次のコード。

function ProcessThread(func) {
  setTimeout(function() {
    func.shift()();
    if (func) {
      setTimeout(arguments.callee, 1000);
    }
  }, 1000);
}
ProcessThread([f1, f2, f3]);

JavaScript Timer-Based Pseudo-Threadingではコーディング上のテクニックとして「func.shift()();」と「setTimeout(arguments.callee, 1000);」を使っている。func.shift()は配列から最初のオブジェクトを取り出して削除するという処理。取り出したコブジェクトに対して関数処理が実施されるから、この指定で「配列の最初の関数を取り出して実行」ということになる。

arguments.calleeは現在実行している関数を指すプロパティなので、setTimeout(arguments.callee, 1000);の指定で自分自身(無名関数)を再帰的に呼び出すという処理を実現していることになる。配列に指定された関数がなくなるまで、タイマー指定で関数を実行していくという寸法だ。setTimeout()は動作を勘違いして理解されやすい機能でもあるので、JavaScript Timer-Based Pseudo-Threadingに掲載されているサンプルはsetTimeout()の動作を理解するサンプルコードとしても参考になる。