どうもはじめまして,株式会社はてなのid:cho45です。これから数回に渡り,拙作のJavaScriptのライブラリであるJSDeferredの紹介と,それに関係するJavaScriptにおける非同期の技術に関して解説させていただきます。
JavaSciptにおける非同期処理
非同期処理とは
まずは確認程度に,非同期処理がどういうものかを解説しておきます。先に乱暴にまとめてしまうと,以下の事柄が大切なところです。
- 非同期とは「あとで」という意味
- 同期処理はコードの見た目順に実行される/非同期ではそうならない
- JavaScript の非同期処理は絶対に同期処理にすることができない
JavaScriptにおける非同期処理をいくつか例に出すと,以下のようなものが挙げられます。
リスト1
// XMLHttpRequest の例
var req = new XMLHttpRequest();
req.open('GET', 'http://example.com/', true);
req.onreadystatechange = function (e) {
if (req.readyState == 4)
alert('async!')
}
};
req.send(null);
alert('hello');
リスト2
// setTimeout の例
var sid = setTimeout(function () {
alert('async!');
}, 0);
alert('hello');
リスト3
// イベントの例
document.body.onclick = function (e) {
alert('async!');
};
alert('hello');
これらは,すべて非同期で処理されるものです。非同期かどうかは,最後の alert() が,直前にでてくる function () {} の中の alert() よりも先に表示されることで,すなわち上記の例はすべて hello -< async! と表示されることで確かめられます。
コールバック関数を渡す関数は非同期になっていることも多いですが,同期的に実行されるようなものもあるので,コールバックをとるかどうかではなく,実行順で判断します。JavaScriptはシングルスレッドなので,非同期的に呼ばれるコールバックを登録する関数を呼ぶと,そのコールバックは必ず現在の実行されている関数が終わったあとに呼びだされます。
例にあるように setTimeout でタイムアウト時間を 0 にしたとしても,絶対に渡した関数は即時実行(=同期実行)されません。
このコールバックをとって関数を非同期実行させるのは最もシンプルな方法ですが,見ての通りコードにしたときの書き順と,実行順が変化します。
JSDeferredとは。JSDeferredが解決すること
JSDeferredはJavaScript上の,あらゆる非同期処理を扱うライブラリです。Deferredというのは聞き慣れない言葉ですが,これはJSDeferredがMochiKitのDeferredを参考に作られているからです。非同期処理において,処理はあとに延期され (deferred) ていくため,と僕は覚えています。
詳しいことはあとあと説明しますが,JSDeferredというライブラリは以下の事柄をコンセプトにしています。
- コンパクトであること
- 単純にコード行数が少ないということです。
- スタンドアローンで動くこと
- 他のどんなライブラリやフレームワークにも依存せずに動くということです。実際,JSDeferred の基本機能は setTimeout の挙動のみに依存しています。
- 書きやすいこと
- コールバックによる非同期処理がめんどうくさくて作ったものなので書きやすくないと意味がありません。
JSDeferred自体のコードを読みとくときや,これからの解説を読むうえでこれらが頭に入っていると,理解が早いかと思います。
JavaScriptにおける非同期処理の問題点
最初,JavaScriptにおける非同期処理の例として,setTimeout などを挙げました。これらは全てコールバックをとり,それをあとで実行していくものです。
例えば以下のようなコードを考えてみます。最終的に foo.json, bar.json, baz.json 全ての情報を得たい,という場合です。http.get は URI とコールバック関数をとり XMLHttpRequest を実行する関数として考えてください。
リスト4
http.get("/foo.json", function (dataOfFoo) {
http.get("/bar.json", function (dataOfBar) {
http.get("/baz.json", function (dataOfBaz) {
alert([dataOfFoo, dataOfBar, dataOfBaz]);
});
});
});
このようにだんだんと関数のネストが深くなっていきます。この程度ならまだいいですが,これがさらに数個あったりしたらどうでしょうか。あるいは,以下のように任意の個数のデータを取得しなくてはならないときはどうでしょうか。
リスト5
var wants = ["/foo.json", "/bar.json", "/baz.json"];
正直めんどうくさくありませんか?
このように,単純にコールバックを使って非同期の処理をしていく方法だと,複雑な処理を書くのがとても難しくなります。

