Qでいろいろ試してみた

Node.jsアプリからバックエンドのWeb APIを叩くとき、複数APIを叩いたり、あるAPIの結果を元に別のAPIを叩くなどの制御が必要になることケースがありますが、Nodeだとこういった順序制御にはQを使うのがメジャーな感じっぽいのでQとQ標準のI/OライブラリであるQ-IOでいくつかのパターンを試してみました。

まずQ-IOのhttpライブラリで取得した結果をJSONに変換する処理をかますために以下のような関数を用意しておきます。

function asJson(promise){
  return promise.then(function(response){
    return response.body.read();
  }).then(function(body){
    return JSON.parse(body.toString("UTF-8"));
  });
}

まずはAPIの呼び出しがチェーンする場合。一つ目のAPIの結果を使って二つ目のAPIを叩き、取得したデータはSailsのビューに渡すことを想定したコードです。

var Q    = require("q");
var HTTP = require("q-io/http");

Q.fcall(function(){
  return asJson(HTTP.request('http://localhost:9000/message/123'))
}).then(function(message){
  return [message, asJson(HTTP.request('http://localhost:9000/user/' + message.messageId))];
}).spread(function(message, user){
  res.view({
    message : message,
    user    : user
  });
});

複数APIを並列に叩くだけならQ.allを使うとシンプルに記述できます。

Q.all([
  asJson(HTTP.request('http://localhost:9000/message/123')),
  asJson(HTTP.request('http://localhost:9000/comments/123'))
]).spread(function(message, comments){
  res.view({
    message  : message,
    comments : comments
  });
});

チェーンするものと並列に叩くものを組み合わせた場合。Qの使い方を覚えれば自由に組み合わせることができますが、最初はちょっと敷居が高いかもしれませんね。

Q.all([
  asJson(HTTP.request('http://localhost:9000/message/123')).then(function(message){
    return asJson(HTTP.request('http://localhost:9000/user/' + message.userId)).then(function(user){
      return {
        message : message,
        user    : user
      };
    });
  }),
  asJson(HTTP.request('http://localhost:9000/comments/123'))
]).spread(function(data, comments){
  res.view({
    message  : data,
    comments : comments
  });
});

ちなみにQを使う場合、QのPromiseを返すライブラリや、Q側で対応しているライブラリを使う場合は上記のように比較的シンプルに記述することができるのですが、そうでないものを使う場合は自分でDeferを使ってPromiseを生成するコードを記述する必要があるので微妙に面倒そうです。