JavaScript | 非同期処理:Promise 応用 – 非同期関数の再利用

JavaScript JavaScript
スポンサーリンク

「非同期関数の再利用」を一言でいうと

非同期関数の再利用は、
「一度作った Promise ベースの処理を、“場面だけ変えて何度も使い回せる形” に設計すること」
です。

言い換えると、

同じような API 呼び出しや、同じようなエラーハンドリングを
毎回コピペで書くのではなく、
「1 回ちゃんと関数にして、“パラメータだけ変えて使う”」 状態まで持っていくことです。

ここが重要です。
Promise の世界では、
「Promise を返す関数」=「再利用できる非同期部品」
と考えて設計していくと、とても扱いやすくなります。


再利用しやすい「Promise を返す関数」の基本形

まずは「生 Promise」を書かず、関数に包む

初心者が最初にやりがちなのが、
ファイルのトップレベルにいきなり new Promise(...) を書いてしまうことです。

// あまり良くない例(その場かぎり)
const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("データ");
  }, 1000);
});

p.then((data) => {
  console.log(data);
});
JavaScript

これだと、「1 回きり」で終わります。
もう一度同じ処理をしたいときは、また new Promise を書き直すことになります。

再利用したいなら、
「Promise を返す関数」にしておく のが基本です。

function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("データ");
    }, 1000);
  });
}

// 何度でも使える
fetchData().then((data) => console.log("1回目:", data));
fetchData().then((data) => console.log("2回目:", data));
JavaScript

引数で「やりたいこと」を変えられるようにする

さらに再利用度を上げるには、
「引数で違いを表現できるようにする」 のがポイントです。

function delay(ms, value) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(value);
    }, ms);
  });
}

// いろいろ使える
delay(1000, "A").then((v) => console.log("1秒後:", v));
delay(2000, "B").then((v) => console.log("2秒後:", v));
JavaScript

delay というたった 1 つの関数で、
「何秒後に何をしたいか」を変えられます。

ここが重要です。
「再利用可能な非同期関数」は、
1回限りの Promise ではなく、
“引数を変えて何度でも呼べる Promise を返す関数”として設計する

ところから始まります。


API 呼び出し処理を「再利用できる形」にする

悪い例:毎回、直書きの fetch

例えば、画面のあちこちでこう書いているとします。

// 画面A
fetch("/api/user")
  .then((res) => res.json())
  .then((data) => console.log("A で使う:", data))
  .catch((err) => console.error(err));

// 画面B
fetch("/api/user")
  .then((res) => res.json())
  .then((data) => console.log("B で使う:", data))
  .catch((err) => console.error(err));
JavaScript

完全に同じことを、場所だけ変えて書いています。
コピペが増えると、
エラーハンドリングを変えたいときに全部直さないといけない、という地獄になります。

良い例:共通の「非同期関数」に切り出す

これを「再利用可能な非同期関数」にしましょう。

function fetchJson(url) {
  return fetch(url).then((res) => {
    if (!res.ok) {
      throw new Error(`HTTP エラー: ${res.status}`);
    }
    return res.json();
  });
}
JavaScript

使う側はこうなります。

// 画面A
fetchJson("/api/user")
  .then((user) => {
    console.log("A で使う:", user);
  })
  .catch((err) => {
    console.error("A でのエラー:", err);
  });

// 画面B
fetchJson("/api/user")
  .then((user) => {
    console.log("B で使う:", user);
  })
  .catch((err) => {
    console.error("B でのエラー:", err);
  });
JavaScript

共通化したことで、

HTTP ステータスのエラーチェック
.json() のパース

など、「どの画面でも共通な処理」を関数の中に閉じ込められました。

ここが重要です。
非同期処理を再利用したいときは、「呼び出し側でよく書いている同じパターン」を見つけて、それを Promise を返す関数にまとめる。
呼び出し側には“結果だけ”を渡せるようにすると、コードが一気にきれいになる。


「小さな非同期関数」を組み合わせて再利用性を上げる

1 個で全部やる関数は再利用しにくい

例えば、こういう関数があります。

function loadUserAndPosts() {
  return fetch("/api/user")
    .then((res) => res.json())
    .then((user) => {
      return fetch(`/api/posts?userId=${user.id}`)
        .then((res) => res.json())
        .then((posts) => {
          return { user, posts };
        });
    });
}
JavaScript

これ自体は便利ですが、

「ユーザーだけ欲しい」場面
「投稿だけ欲しい」場面

では再利用しづらいです。

「小さな非同期部品」に分けておく

まずは、粒度の小さい非同期関数を作ります。

function fetchUser() {
  return fetchJson("/api/user");
}

function fetchPostsByUser(userId) {
  return fetchJson(`/api/posts?userId=${userId}`);
}
JavaScript

これらを組み合わせて、
「ユーザー+投稿」関数を作ればよいです。

function loadUserAndPosts() {
  return fetchUser().then((user) => {
    return fetchPostsByUser(user.id).then((posts) => {
      return { user, posts };
    });
  });
}
JavaScript

こうすると、

ユーザーだけ必要 → fetchUser()
投稿だけ必要 → fetchPostsByUser(id)
両方必要 → loadUserAndPosts()

というふうに、
状況に応じて「部品」か「組み合わせ」かを選べる ようになります。

ここが重要です。
再利用性を上げるには、
「でかい非同期関数」 1 個で済ませるのではなく、
小さな Promise 関数をいくつか作って、それを組み合わせる構造にすること。
小さい部品ほど再利用しやすい。


「共通処理」をラップして再利用する

タイムアウト・リトライなどを「共通化」する

たとえば、いろんな API 呼び出しにタイムアウトをつけたいとします。

毎回こう書くのはツラいです。

function fetchWithTimeout(url, ms) {
  return new Promise((resolve, reject) => {
    const id = setTimeout(() => reject(new Error("タイムアウト")), ms);

    fetch(url)
      .then((res) => {
        clearTimeout(id);
        resolve(res);
      })
      .catch((err) => {
        clearTimeout(id);
        reject(err);
      });
  });
}
JavaScript

これを「再利用可能なラップ関数」として定義しておけば、
どの API にも簡単につけられます。

function withTimeout(promiseFactory, ms) {
  return (...args) =>
    new Promise((resolve, reject) => {
      const id = setTimeout(() => reject(new Error("タイムアウト")), ms);

      promiseFactory(...args)
        .then((res) => {
          clearTimeout(id);
          resolve(res);
        })
        .catch((err) => {
          clearTimeout(id);
          reject(err);
        });
    });
}
JavaScript

既存の関数を「タイムアウト付き」にする:

const fetchJsonWithTimeout = withTimeout(fetchJson, 3000);

fetchJsonWithTimeout("/api/user")
  .then((user) => console.log(user))
  .catch((err) => console.error(err));
JavaScript

ここでのポイントは、

withTimeout 自体が「非同期関数を再利用するための“メタ関数”」になっている
元の fetchJson を一切触らず、「タイムアウト付き版」を量産できる

ということです。

ここが重要です。
再利用のレベルが上がってくると、「非同期関数そのもの」だけでなく、「非同期関数に共通機能を足すラッパー」も再利用部品になる。
これによって、「どんな非同期処理にも同じルールを簡単に適用できる」ようになる。


「戻り値の形」を揃えるともっと再利用しやすくなる

成功 / 失敗を「例外」ではなく「値」で返す

ときどき便利なのが、
「失敗しても throw せず、“結果オブジェクト”として返す」スタイル の再利用関数です。

例えば、こういう関数を作ります。

function safeFetchJson(url) {
  return fetchJson(url)
    .then((data) => {
      return { ok: true, data };
    })
    .catch((error) => {
      return { ok: false, error };
    });
}
JavaScript

使う側:

safeFetchJson("/api/user").then((result) => {
  if (result.ok) {
    console.log("成功:", result.data);
  } else {
    console.error("失敗:", result.error);
  }
});
JavaScript

このパターンの良いところは、

catch を多用せず、「成功 / 失敗をひとまとめに扱える」
複数の API を Promise.all で並列実行しても、全部の結果を「配列の中身」として扱える

など、「扱う側の条件分岐がシンプルになる」 ところです。

ここが重要です。
非同期関数の再利用性を上げるためには、「返す形(成功 / 失敗の表現)」をある程度パターン化しておくと、呼び出し側のコードが揃って読みやすくなる。


初心者向け「非同期関数の再利用」の押さえどころ

最後に、本当に大事なポイントだけを整理します。

非同期処理を「その場の Promise」で書き捨てず、必ず「Promise を返す関数」に切り出す。

引数で動作を変えられるようにして、「同じ関数をいろんな場面から呼べる」形にする。

小さい非同期関数(fetchUser, fetchPosts など)を複数作り、それを組み合わせて大きな処理を作ると再利用性が上がる。

共通ロジック(fetch + エラーチェック、タイムアウト、リトライなど)は「ラッパー関数」として 1 箇所にまとめる。

返り値の形(成功 / 失敗の表現)をある程度揃えると、呼び出し側のコードがスッキリし、再利用しやすくなる。

ここが重要です。
非同期処理を「1 回きりのイベント」ではなく、「何度も呼べる部品」にする意識を持つこと。
そのために、“Promise を返す関数として切り出す → 小さく分解して組み合わせる → 共通処理はラップして共有する” という流れを習慣にすると、一気にコードの質が変わります。

練習としては、

いま自分が書いた非同期コードの中から、「同じような fetch + then + catch の塊」を1つ見つける
それを「Promise を返す関数」に切り出し、2 箇所以上から呼ぶ形に書き換えてみる

これを 1 回やってみると、
「あ、こうすれば何度でも同じ非同期処理をきれいに使い回せるんだ」
という感覚が、かなりハッキリ掴めてくるはずです。

タイトルとURLをコピーしました