【JavaScript】APIのレスポンスを組み合わせて複数種のオブジェクトを作る際、同じリクエストを送るのを避ける方法

 図の様に API の結果を組み合わせて JavaScript 内で使うオブジェクトを作る必要があるとします。

 これを実現する素朴なコードは次です。

async function fetchAuthentication() {
  // 認証情報取得APIを叩くコード
}

async function fetchProfile() {
  // プロフィールAPIを叩くコード
}

async function fetchSpecialMember() {
  // 特別会員APIを叩くコード
}

async function showProfile() {
  return Promise.all([fetchAuthentication(), fetchProfile()])
    .then(([authData, profileData]) => {
      // クライアント内でのプロフィールにまとめるコード
    });
}

async function showAsSpecialMember() {
  return Promise.all([fetchAuthentication(), fetchSpecialMember()])
    .then(([authData, specialMemberData]) => {
      // クライアント内での特別会員にまとめるコード
    });
}

 それぞれ独立しておりプロフィール、特別会員を JavaScript の中で呼び出したい時に扱いやすいコードです。このコードならば適宜 showXXXX を叩くことで一つのまとまった情報を得られます。しかしながらこのコードの場合、プロフィールと特別会員を同時に扱う際に認証APIを重複して叩いてしまいます。これはサーバー側の負荷を余分に大きくしています。これを軽減するために同じAPIを叩く時は一度だけにしたいです。

 これを実現する方法は二種類あります。一つはメモ化です。こちらはデータがあまり変わらず、何度も断続的に参照する際に便利に使える方法です。これは次の様にできます。

let authenticationCache = null;

async function fetchAuthentication() {
  // 一度結果を取得済みならAPIを叩かず、その結果を返す
  if (authenticationCache) return authenticationCache;

  // 認証情報取得APIを叩くコード
  authenticationCache = // 取得した認証情報
  return authenticationCache;
}

async function fetchProfile() {
  // プロフィールAPIを叩くコード
}

async function fetchSpecialMember() {
  // 特別会員APIを叩くコード
}

async function showProfile() {
  return Promise.all([fetchAuthentication(), fetchProfile()])
    .then(([authData, profileData]) => {
      // クライアント内でのプロフィールにまとめるコード
    });
}

async function showAsSpecialMember() {
  return Promise.all([fetchAuthentication(), fetchSpecialMember()])
    .then(([authData, specialMemberData]) => {
      // クライアント内での特別会員にまとめるコード
    });
}

 一度、情報を取得したらそれを使いまわす形です。これならば何度もAPIを叩くことはありません。実装も比較的シンプルな変更で済みます。一方で不便な点もあります。例えば情報の取得が極めて短時間に何度も走る場合です。この場合、キャッシュを参照できる様になるまでに何度もAPIを叩いてしまいます。排他制御的にできなくもないですがコードが複雑になりがちです。また情報の変化が激しいものを参照する場合、キャッシュをいつまで参照するのかという問題が出てきます。古い情報を元に処理を進めるとエラーになりがちでユーザーに不便を強いてしまいます。

 もう一つの方法は一度の取得 Proimise を使いまわす方法です。これは次の様にできます。

async function fetchAuthentication() {
  // 認証情報取得APIを叩くコード
}

async function fetchProfile() {
  // プロフィールAPIを叩くコード
}

async function fetchSpecialMember() {
  // 特別会員APIを叩くコード
}

async function showProfile(authenticationPromise) {
  return Promise.all([authenticationPromise, fetchProfile()])
    .then(([authData, profileData]) => {
      // クライアント内でのプロフィールにまとめるコード
    });
}

async function showAsSpecialMember(authenticationPromise) {
  return Promise.all([authenticationPromise, fetchSpecialMember()])
    .then(([authData, specialMemberData]) => {
      // クライアント内での特別会員にまとめるコード
    });
}

async function main(){
  // 認証情報APIの結果を得る Promise を返す関数をあらかじめ呼んでおき
  const authenticationPromise = fetchAuthentication();
  const [profile, specialMember] = await Promise.all([
    // 同じ Promise を結果を合成してオブジェクトにする関数に渡す
    showProfile(authenticationPromise),
    showAsSpecialMember(authenticationPromise)
  ]);
  // プロフィールと特別会員を使ってなんやかんやするコード
}

 この方法はAPIを叩くPromiseオブジェクトを共有する方法です。こうすることで一度のリクエストの結果を複数個所で最速で使えます。この方法は同時実行でも重複リクエストを送らず、最新情報が反映されるためメモ化の欠点を補えます。一方でメモ化に比べてAPIを叩く回数が増え、コードの結合が密となって扱いにくくもなります。

 どちらも一長一短であり使いどころは適宜決める必要があります。またパフォーマンス上で問題がないのであれば、これらの方法を使わずに読みやすいコードを保った方がプログラムの改修が楽になりより快適な開発をするという方針も取れます。

>株式会社シーポイントラボ

株式会社シーポイントラボ

TEL:053-543-9889
営業時間:9:00~18:00(月〜金)
住所:〒432-8003
   静岡県浜松市中央区和地山3-1-7
   浜松イノベーションキューブ 315
※ご来社の際はインターホンで「316」をお呼びください

CTR IMG