巷では、やれ Ajax だとか、RIAだとか騒がれていますが、Ajax の非同期性は基礎がなってない、というお話です。
Emacs
Emacs では、コマンドを start-process で起動したり、TCP コネクションを open-network-stream で開くと、それは非同期プロセスに見えます。こんな感じでコードを書きます。
(defun my-filter (process string) ;; string を適当に処理 ) (defun my-sentinel (process event) ;; 終了処理 (異常終了の処理とか) ) (defun my-connection () (let ((process (open-network-stream プロセス名 バッファ名 ホスト名 ポート))) (set-process-filter process 'my-filter) (set-process-sentinel process 'my-sentinel) (process-send-string process 命令)))
非同期プロセスから入力があると、その都度フィルタが呼ばれます。このように、通信の途中でフィルタが呼ばれるので、通信の終了を待たずに入力を処理できます。
実装としては、古典的な select() で実現されています。Emacs では、決してリッチな体験はできませんが、基本はとってもしっかりしています。
Ajax
Ajax の典型的なコードは、以下のようになります。
var request = new XMLHttpRequest(); request.onreadystatechange = function() { if (request.readyState == 3) { // ブラウザによって挙動が違う } else (request.readyState == 4) { // request.responseText を処理 } } request.open('GET', 'foo.txt', true); request.send(null);
readyState は、3 が「データの取得中」、4が「データの取得完了」です。Emacs に例えるなら、前者がフィルタ(filter)、後者が番兵(sentinel)となります。
readyState 3 の挙動はブラウザによってマチマチです。Firefox なら、ちゃんと適当なタイミングで指定した関数を読んでくれますが、IE では状態が変わった際に一回だけ関数を呼び、しかも responseText を参照するとエラーになります。
つまり、readyState 3 は事実上使えないのです。
比較してみる
複数のデータを取得したいとしましょう。
クライアントが データのID を指定すると、サーバはデータを返すとします。複数の ID を列挙しておけば、複数のデータが順に返って来ます。
Emacs なら、複数の ID を指定し、フィルタでデータを順に処理して行けます。データの順番は守られます。
Ajax だと、readyState 4 を使うしかないので、複数個の Ajax オブジェクトを生成し、それぞれが ID を 1 つ指定して、戻ってきたデータをそれぞれ処理することになります。データの順番は守られないので、別途データを並べ直す仕組みを考える必要があります。
これでうまくいくアプリケーションもあるでしょう。しかし、順番が守られていないとうまくいかないアプリケーションもあります。
その場合は結局、1 つの Ajax オブジェクトから、複数の ID をサーバへ指定し、すべてのデータを取得した後で、処理することになります。これは、「ユーザの操作」と「通信」は非同期になっていますが、「通信」と「描画」は非同期にならないことを意味しています。
そう考えると、Ajax を標榜しながら、そんな制約のある Web アプリケーションって多いと思いませんか?