Tame is an extension to JavaScript that makes event programming easier to write, read, and edit.

JavaScriptで非同期処理を記述したことがあるプログラマであれば、その記述の面倒さに閉口したことがあるだろう。たしかに、パフォーマンスを考えるとシーケンシャルに処理するよりもパラレルに処理させた方が有利なことが多い。しかし、記述がかなり煩雑なものになるため、あとからデバッグやアレンジ、ほかの非同期処理のマージなどを実施しづらい。

この問題に対するひとつの解決方法として活用できるライブラリが登場した。「TameJS」がそれだ。TameJSは日に1億のHTTPリクエストをさばいているサービスOkCupidの開発者らが開発したライブラリ。非同期処理を簡単に記述できるようにするという特徴がある。

もともとのアイディアはC++で実装され数年に渡って活用されてきたもので、今回それを汎用的に利用できるJavaScriptライブラリとして実装したものが「TameJS」となる。MITライセンスのもとオープンソースソフトウェアとして公開されており、Node.jsやその他のV8プロジェクトで活用できる。

TameJSで実現できることは、TameJSのサイトに掲載されているサンプルを読むとわかりやすい。まず、次のような非同期処理が掲載されている。複雑なロジックを処理するものではないが、すでにかなり見にくい状態といえる。

handleVisit : function(angel, buffy) {
  getScore(angel, buffy, function(match_score) {
    getNextMatch(angel, function(next_match) {
      recordVisitAndGetInfo(angel, buffy, function(visit_info) {
        if (match_score > 0.9 && ! visit_info.last_visit) {
          sendVisitorEmail(angel, buffy);
        }
        doSomeFinalThings(match_score, next_match, visit_info);
      });
    });
  });
}

これをTameJSを使って書き換えると次のようになる。awaitとdeferという指定が追加されているところがポイントとなる。この組み合わせで3つの関数を並列に処理し、すべての処理が終わったらその下に記載されているコードがコールバックとして処理されている。

handleVisit : function(angel, buffy) {
        //
        // let's fire all 3 at once
        //
        await {
                getScore (angel, buffy, defer(var score));
                getNextMatch (angel, buffy, defer(var next));
                recordVisitAndGetInfo (angel, buffy, defer(var vinfo));
        }
        //
        // they've called back, and now we have our data
        //
        if (score > 0.9 && ! vinfo.last_visit) {
                sendVisitorEmail(angel, buffy);
        }
        doSomeFinalThings(score, next, vinfo);
}

TameJSを使ったコードの方がすっきりしていて理解しやすい。どの関数が並列で処理されるのかが明確で、コールバック処理もわかりやすい。TameJSのサイトには、実際にNode.jsで動作するパラレルDNSリゾルバも掲載されている。

var dns = require("dns");
function do_one (ev, host) {
    await dns.resolve (host, "A", defer (var err, ip)); 
    if (err) { console.log ("ERROR! " + err); } 
    else { console.log (host + " -> " + ip); }
    ev();
};
function do_all (lst) {
    await {
        for (var i = 0; i < lst.length; i++) {
            do_one (defer (), lst[i]);
        }
    }
};
do_all (process.argv.slice (2));

並列処理がわかりやすく実装されていることがわかる。TameJSは単体のライブラリとして提供されており、NodeやV8エンジンに搭載されているわけではない。しかしアイディアは興味深く、実装もわかりやすい。今後普及する可能性があるライブラリとして注目される。