Loading
  • LIGHT

  • DARK

ROUTE

ルートゼロの
アクティビティ

TypeScript Promise型定義完全解説|any撲滅と使い分け

3

はじめに|この記事で得られる価値

「async/awaitは普段から使ってるけど、Promiseの型定義がなんとなく“any”頼み…。
エラーハンドリングも自己流で、後輩レビューのたびにソワソワ…。」
こんな“開発者あるある”、身に覚えありませんか?
私たちも現場で同じようにモヤモヤしてきました。
本記事では、その悩みを“一緒に”整理し、自信を持って使いこなせるPromiseの型・使い分け・アンチパターン回避まで、図とコード例で伴走します。


1. Promiseって何?— async/awaitの“裏側”で動く非同期処理の正体

■ 用語と仕組み:「Promise」とは?

「async/await使えてるのに、なぜPromiseを意識しないといけないの?」そんな疑問、よく聞きます。
Promiseとは、ざっくり言えば「非同期処理の結果を持つ“箱”」
例えばAPIデータの取得やファイル操作――“今は結果がないけど、未来に値が入る”そんな箱です。

(非同期処理の基礎やJavaScriptの実例については『非同期処理とは?JavaScriptとJavaの実例でわかる使い方・落とし穴・解決法』をご参照ください)

用語解説:Promise
JavaScriptで非同期処理の「成功・失敗・進行中」の状態や結果を管理するための仕組み。API通信やファイル読み込みなど、すぐに結果が出ない処理の「完了」や「失敗」を後から受け取れる“箱”のような存在。

用語解説:非同期処理
プログラムの実行中に、他の処理を待たずに進める仕組み。例えば「データ取得が終わるまで他の作業も同時進行できる」イメージ。

用語解説:API
他のサービスやシステムとデータをやり取りするための「窓口」や「取り決め」。

  • pending(待機中)…非同期処理の最中
  • fulfilled(成功)…無事に値が入った状態
  • rejected(失敗)…エラー(理由)が入った状態

一度「fulfilled」か「rejected」になれば結果は不変です。

ここでポイント。
async/awaitはPromiseの“糖衣構文”、つまり本質はPromise。直感的に書けるだけで、裏側は全てPromiseが制御しています。

用語解説:async/await
JavaScriptでPromiseを「直感的・簡単」に書くための文法。asyncを付けた関数は必ずPromiseを返し、awaitで「Promiseの完了を待つ」ことができる。

用語解説:糖衣構文(シンタックスシュガー)
本質的な動作は同じだが、より分かりやすく・書きやすくするための文法上の工夫。

// async関数は必ずPromiseを返します
async function fetchData(): Promise<string> {
  return "データ";
}
// 上は内部的にPromise.resolve("データ")と同じ意味

複雑な非同期制御やエラー処理をちゃんとしたいなら、Promiseの正体から理解しておくのが近道です。

(async/awaitのバグや使い分けについては『async関数でthenが使えない?原因と対策を完全解説』や『JavaScript thisの違い・バグ事例7選|アロー関数やbindの迷わない使い分け』も参考になります)


2. any撲滅!Promiseの型定義3パターン

■ 正しい型定義で「型安全」と保守性UP

「ついPromiseの型定義がanyに…。型安全、どこいった?」
この悩み、私たちも通りました。
TypeScriptでPromiseを扱う際は「正しい型定義」が最重要。anyを安易に使えば型安全は崩壊します。

(TypeScriptの型や開発環境の始め方は『TypeScriptとは?JavaScriptとの違いを初心者向けにわかりやすく解説』や『TypeScriptの始め方|Node.jsとVSCodeで学ぶ開発環境構築ガイド【初心者向け完全解説】』もご参照ください)

用語解説:型安全
プログラム中で「値の種類(型)」が正しく守られている状態。型が合わないミスを事前に防げる。

用語解説:any型
TypeScriptで「どんな型でもOK」として扱う危険な型。型安全が失われる原因。

用語解説:TypeScript
JavaScriptに「型」の仕組みを加えた言語。大規模開発や保守性向上に役立つ。

■ 基本:Promise<T>で型を明示する

成功時に返す値の型は、Promise<T>で指定します。
Tにはstringnumber、自作のinterfaceなどOK。

用語解説:interface
TypeScriptで「オブジェクトの形(プロパティや型)」を定義する仕組み。型チェックや自動補完に役立つ。

interface User {
  id: number;
  name: string;
}

function fetchUser(userId: string): Promise<User> {
  return new Promise((resolve, reject) => {
    const user: User = { id: 1, name: "Taro Yamada" };
    if (user) resolve(user);
    else reject(new Error("User not found."));
  });
}

fetchUser("1").then(user => {
  console.log(user.name); // userはUser型で推論
});

■ async関数は返り値の型推論にお任せ

interface Product {
  id: string;
  price: number;
}

// 型注釈なしでもOK
async function fetchProduct(productId: string) {
  const response = await fetch(`/api/products/${productId}`);
  if (!response.ok) throw new Error("Network response was not ok");
  return await response.json() as Product;
}

// 型を明示して堅牢にする場合
async function fetchProductExplicit(productId: string): Promise<Product> {
  return { id: productId, price: 1000 };
}

■ catchのエラー型はunknown+型ガードが鉄則

async function execute() {
  try {
    throw "文字列エラー";
  } catch (error: unknown) {
    console.error(getErrorMessage(error));
  }
}

function getErrorMessage(error: unknown): string {
  if (error instanceof Error) return error.message;
  if (typeof error === 'string') return error;
  return 'An unknown error occurred.';
}

unknown型+型ガードで予期せぬエラーにも安心対応!

(JavaScriptのよくあるエラーや原因・対策は『JavaScriptでよく発生するエラー「ReferenceError」「SyntaxError」などの原因と対策を具体例付きで解説』も参考になります)

用語解説:unknown型
TypeScriptで「どんな型か分からない」ことを明示する型。anyより安全で、使うときは型チェック(型ガード)が必須。

用語解説:型ガード
値の型をif文などで判定し、安全に使うためのテクニック。


3. 静的メソッドの使い分けフローチャート—all, allSettled, race, anyの違いは?

■ 複数の非同期処理、どうまとめる?

Promiseには用途ごとに使い分けるべき静的メソッドが揃っています。

用語解説:静的メソッド
クラスやオブジェクトに属する「インスタンス化せずに使える」関数。Promise.allなどは直接Promiseから呼び出せる。

1. 全部“成功”前提ならPromise.all

「全部のAPIが揃わないとダメ!」ならPromise.allです。
一つでも失敗(rejected)すると、全体が即失敗になるので注意。

(APIの基礎や現場での活用法は『APIとは?SES現場で役立つ基礎〜Postman活用まで完全ガイド』もご参照ください)

用語解説:Promise.all
複数のPromise(非同期処理)をまとめて実行し、全部成功したら結果を配列で返す。1つでも失敗すると全体が失敗になる。

async function getDashboardData() {
  try {
    const [user, posts, settings] = await Promise.all([
      fetch('/api/user'),
      fetch('/api/posts'),
      fetch('/api/settings')
    ]);
    console.log('All data loaded!');
  } catch (error) {
    console.error("Failed to load one of the resources:", error);
  }
}

2. 一部失敗OK・全部の結果が欲しいならallSettled

用語解説:Promise.allSettled
複数のPromiseをまとめて実行し、全部の「成功・失敗」結果を配列で返す。失敗があっても全体は止まらない。

async function checkServiceStatus() {
  const services = ['/api/auth', '/api/payment', '/api/invalid-endpoint'];
  const results = await Promise.allSettled(
    services.map(url => fetch(url))
  );
  results.forEach((result, index) => {
    if (result.status === 'fulfilled') {
      console.log(`Service ${services[index]} is OK.`);
    } else {
      console.error(`Service ${services[index]} is down:`, result.reason);
    }
  });
}

3. 一番早い結果だけ採用したいならrace

用語解説:Promise.race
複数のPromiseのうち、一番早く完了したものの結果だけを返す。残りは無視される。

function requestWithTimeout(url: string, timeout: number): Promise<Response> {
  const timeoutPromise = new Promise<never>((_, reject) =>
    setTimeout(() => reject(new Error('Request timed out')), timeout)
  );
  return Promise.race([fetch(url), timeoutPromise]);
}

requestWithTimeout('/api/heavy-process', 5000)
  .then(response => console.log('Got response:', response))
  .catch(error => console.error(error.message));

4. どれか一つ“成功”でいいならany

用語解説:Promise.any
複数のPromiseのうち、どれか1つでも成功したらその結果を返す。全部失敗した場合だけエラーになる。

async function fetchFromFastestCDN() {
  const cdnEndpoints = [
    'https://cdn1.example.com/resource',
    'https://cdn2.example.com/resource',
    'https://cdn3.example.com/resource'
  ];
  try {
    const fastestResponse = await Promise.any(
      cdnEndpoints.map(url => fetch(url))
    );
    console.log('Fetched from fastest CDN:', fastestResponse.url);
  } catch (error) {
    console.error('All CDNs are unavailable.', error);
  }
}

4. コードレビューで指摘したい!Promiseアンチパターン5選

■ 「やりがち」NG例とOK例をセットで

用語解説:アンチパターン
よくある「やってはいけない書き方」や「失敗しやすい実装例」。なぜNGか理由も理解しておくと良い。

用語解説:コードレビュー
他の開発者がソースコードをチェックし、ミスや改善点を指摘する工程。品質向上や学び合いに役立つ。

  1. new Promiseの中でasync/awaitを使う

    async関数はそれ自体がPromise。内部でnew Promiseは冗長です。
    NG例:

    function ngFetch(): Promise<string> {
      return new Promise(async (resolve, reject) => {
        try {
          const response = await fetch('/api/data');
          resolve(await response.text());
        } catch (e) {
          reject(e);
        }
      });
    }
    

    OK例:

    async function okFetch(): Promise<string> {
      const response = await fetch('/api/data');
      return response.text();
    }
    
  2. ループ内でawait、並列化できてない

    awaitをループ内で都度実行すると、直列処理に…。
    NG例:

    async function ngFetchUsers(ids: string[]) {
      const users = [];
      for (const id of ids) {
        users.push(await fetch(`/api/users/${id}`));
      }
      return Promise.all(users);
    }
    

    OK例:

    async function okFetchUsers(ids: string[]) {
      const userPromises = ids.map(id => fetch(`/api/users/${id}`));
      return await Promise.all(userPromises);
    }
    
  3. .then()のreturn漏れ

    .then()の中でreturnを書き忘れると、次の.then()に値が渡りません。
    NG例:

    fetch('/api/user/1')
      .then(response => response.json())
      .then(user => {
        console.log(user.name);
        // returnを書き忘れ
      })
      .then(result => {
        console.log(result); // undefined
      });
    

    OK例:

    fetch('/api/user/1')
      .then(response => response.json())
      .then(user => {
        console.log(user.name);
        return user.id; // 明示的にreturn
      })
      .then(userId => {
        console.log(userId);
      });
    
  4. async関数なのにPromiseを返してしまう

    asyncをつけた時点でPromise化されるので、Promise.resolveで包むのは冗長。
    NG例:

    async function ngGetValue(): Promise<string> {
      return Promise.resolve('value');
    }
    

    OK例:

    async function okGetValue(): Promise<string> {
      return 'value';
    }
    
  5. try-catchや.catch()でエラーを握りつぶす

    エラー未処理のPromiseは“unhandled promise rejection”となり、想定外のバグ要因に。
    NG例:

    async function mightFail() {
      throw new Error('Something went wrong');
    }
    mightFail(); // エラーキャッチしていない
    

    OK例:

    async function main() {
      try {
        await mightFail();
      } catch (e) {
        console.error("Caught an error:", e);
      }
    }
    mightFail().catch(e => console.error("Caught an error:", e));
    main();
    

5. まとめ|「なぜそう書くか?」まで腹落ちしたPromise設計を

  • Promiseは「非同期の状態を持つ箱」
  • async/awaitはPromiseを扱いやすくする糖衣構文
  • Promise<T>型、型推論、unknown型ガードで“any”を撃退
  • all, allSettled, race, anyはユースケースごとに適切に選択
  • アンチパターンを把握して、保守性の高いコードを意識

用語解説:保守性
コードが「直しやすい」「拡張しやすい」など、長期的に管理しやすい性質。

用語解説:ユースケース
どんな場面・目的で使うかという具体的な利用例や想定シナリオ。

“なんとなく書ける”から“なぜそう書くべきか”が腹落ちすれば、
あなたのコード品質も、チームの生産性もグンと高まります。

ぜひコードをコピペして、まずは動かしてみてください。


FAQ|よくある疑問に回答

  • Q. Promise<void>はどんなとき使う?
    A. 返り値が不要な非同期処理で使います。例:DB更新API呼び出しなど「完了だけ待てばOK」なとき。
  • Q. .then()の中でfetchを呼ぶのはNG?
    A. 必ずしもNGではありませんが、ネストが深くなるならasync/awaitでフラットに書き直すと可読性UPです。
  • Q. async関数にtry-catchなしだとどうなる?
    A. エラー未捕捉のPromiseは「unhandled promise rejection」となり、ブラウザならコンソールエラー、Node.jsならバージョン次第でプロセスクラッシュも。必ずどこかでキャッチしましょう。
  • Q. Promise.resolve()やPromise.reject()はいつ使う?
    A. テストやモック、値を強制的にPromise化したい時など。「同期値をPromiseチェーンに載せたい」場面で便利です。

用語解説:void型
「何も値を返さない」ことを明示する型。Promiseは「完了したらOK」という非同期処理で使う。

用語解説:ネスト
if文や関数などが「入れ子」になって深くなること。深いと読みにくくバグの温床になる。

用語解説:unhandled promise rejection
Promiseのエラーがどこにも捕捉されず放置された状態。バグや予期せぬ動作の原因になる。

用語解説:モック
テスト用に本物の代わりとなる「ダミーの処理やデータ」を用意すること。


もっとルートゼロを知りたいなら

DISCOVER MORE