« Japanize - ウェブサイトの高速化 | メイン | Japanize が Firefox にフィッシングサイト扱いされている件について »

2007年05月25日

JavaScript/1.7 で協調的マルチスレッド

 JavaScript/1.7 の話なので Firefox 限定です。以前、

 残念ながら、JavaScript には非同期メッセージを同期化する方法はない!残念!><ほんとに残念!

 JavaScript 1.7 なら yield でできるんじゃないかと思いました。

Kazuho@Cybozu Labs: JavaScript で非同期処理

と書いたっきり失念していたのですが、社内で話題になったので実装してみました。こんな感じです。

function runnable(f) {
  var o;
  o = f(function () { o.next(); });
  o.next();
}

runnable(function (next) {
  // 初期化
  var answer = Math.floor(Math.random() * 10 + 1);
  // ボタンが押されるまで yield で待機するよう設定
  document.getElementById('guessme_button').onclick = next;
  // ループ
  while (1) {
    yield;
    var guess = document.getElementById('guessme_value').value;
    if (guess < answer) {
      alert('もっと大きいよ');
    } else if (guess > answer) {
      alert('もっと小さいよ');
    } else {
      alert('大正解!');
      break;
    }
  }
  // リプレイ
  document.getElementById('guessme_value').value = '';
  runnable(arguments.callee);
});

 見てのとおり、1-10 の間の数字を当てるゲームです。↓で遊べます。

数字を入力: (1-10)   

 正直、最初はあまり便利だと思わなかったのですが、ちょっといいかもと思うようになってきました。例えば Firefox の拡張機能で、多数の URL からデータをダウンロードする場合を考えてみます。同期的な処理を行うと、ダウンロードが完了するまで UI をブロックしてしまうため、非同期処理で実装する必要があります。なので、普通はこんな感じで書くことになります。

一般的な書き方
function get_all(urls) { if (urls.length != 0) { // build request var xhr = new XMLHttpRequest(); xhr.open("get", urls.shift(), true); xhr.onreadystatechange = function () { if (xhr.readyState == 4) { // handle response if (xhr.status == 200) { ... } // initiate next request get_all(urls); } }; // send request xhr.send(null); } }

 慣れればどうってことはないのですが、再帰的な処理を行う必要があります。
 それに対し、yield を使うと以下のように書くことができます。

yield を使った書き方
function get_all(urls) { runnable(function (next) { for (var i = 0; i < urls.length; i++) { // build request var xhr = new XMLHttpRequest(); xhr.open("get", urls[i], true); xhr.onreadystatechange = function () { if (xhr.readyState == 4) next(); }; xhr.send(null); // wait for response yield; // handle response if (xhr.status == 200) { ... } } }); }

 再帰呼び出しの必要がなくなりました。すべてのダウンロードが完了するまでスコープの移動がないので、変数の取り回しが楽になります。それにしても協調型の並列処理なんて、もうとっくに墓場送りになっているものだと思っていましたが、まだまだ使う機会があるんですね (笑)

 なお、復帰条件の設定と yield の呼び出しが分かれていて気持ち悪いという向きには、yield の引数として条件を渡すという手もあります。yieldをwait代わりに使っちゃっていいのかなー - outsider reflex は、復帰条件を時間経過に限定するかわりに、そうしているようです。

投稿者 kazuho : 2007年05月25日 13:47 このエントリーを含むはてなブックマーク このエントリーを含むはてなブックマーク

トラックバック

このエントリーのトラックバックURL:
http://labs.cybozu.co.jp/cgi-bin/mt-admin/mt-tbp.cgi/1295

このリストは、次のエントリーを参照しています: JavaScript/1.7 で協調的マルチスレッド:

» [javascript]yieldを使って非同期->同期(続き) from snippets from shinichitomita’s journal
元ネタ: http://labs.cybozu.co.jp/blog/kazuho/archives/2007/05/coopthread.php これ... [続きを読む]

トラックバック時刻: 2007年05月29日 00:27