Clojure/ClojureScriptライブラリのcore.asyncが発表されてから1ヶ月経過していないが、フロントエンドのコードで"コールバック地獄"を避けるためにこのライブラリを効率的に使う方法を説明したブログ記事が多数書かれており、単純なコードで印象深いインブラウザのデモを実現する例も生まれている。
core.asyncはClojureで書かれたライブラリでClojureとClojureScriptで使うことができる。ClojureはJVMでのLispの実装だ。ClojureScriptはClojureの大きなサブセットの実装であり、JavaScriptにコンパイルできる。core.asyncはLispのマクロの威力を示す。ほとんどの他の言語ではcore.asyncが実現することを実現するには言語自体を変更しなければならないが、Lispの場合はマクロを使ってライブラリとして実装できる。
名前の通り、core.asyncは非同期プログラミングをシンプルにする。Goから多くのアイディアを拝借しているが、特にgoroutines (core.asyncのgo blocksにちなんで名付けられた)とチャンネルだ。チャンネルはひとつ以上のパブリッシャと1つ以上のコンシューマで構成されたキューだ。仕組みはシンプルで、キューにデータを置くと、コンシューマがキューからデータを取得する。Clojure/ClojureScriptのデータは不変なので、チャンネルは安全にスレッド間の通信を実現できる。しかし、スレッド間通信が安全に実現できるのは、ClojureScriptにとっては特に面白い機能ではない。JavaScriptはシングルスレッドで動作するからだ。
core.asyncはチャンネルからの読み込みとチャンネルへの書き込みにふたつの方法を提供する。ブロッキング方式とノンブロッキング方式だ。ブロッキング方式では書き込みはチャンネルが書き込める領域(バッファサイズは構成可能だ)を確保できるまでスレッドをブロックする。また読み込みではキュー上の値が読み込めるようになるまでスレッドをブロックする。興味深いのは、チャンネルに対する非同期読み込みと書き込みだ。これらは"go blocks"の中でのみ実行できる。go blocksは同期スタイルで書かれているが、内部では非同期に実行されるステートマシンに変換される。
次のコードはcore.asyncベースのコードだ。
(let [ch (chan)] (go (while true (let [v (<! ch)] (println "Read: " v)))) (go (>! ch "hi") (<! (timeout 5000)) (>! ch "there")))
この例では、letが新しいローカル変数chを導入する。この変数は新しいチャンネルだ。このletのスコープにはふたつのgoブロックが定義されており、最初のブロックはチャンネルであるchから値を変数vに読み込む(<!)無限ループで、読み込んだ値を標準出力に出力する。もうひとつのgoブロックはふたつの値をチャンネルであるchに書き込む(>!)。最初に"hi"を書き込み。5秒待ってから"there"を書き込む。5秒待つのは、timeoutというチャンネルからの読み込みによって実装している。このチャンネルは設定されたタイムアウト時間を過ぎるとクローズする(nilを返す)。このコードをClojure REPLで実行すると、すぐに結果が帰ってくる。"Read: hi"と表示され、5秒後に"Read: there"と表示される。
JavaScriptプログラマなら、このwhileループを見てパニックになってしまうだろう。このような方法でブロッキングのループを実行したら、ブラウザは5秒でフリーズしてしまう。core.asyncの"マジック"は内部で各goブロックの内部をステートマシンに変換し、同期のチャンネル読み込みと書き込みを非同期のコールに変えていることだ。
次に挙げるリソースはcore.asyncについて詳しく学び、フロントエンドの開発に活用するのに役に立つ。