【JavaScript】Promiseとasync/awaitの使い方|非同期処理の基本から実務パターンまで

【JavaScript】Promiseとasync/awaitの使い方|非同期処理の基本から実務パターンまで JavaScript

JavaScriptの非同期処理は、Web開発で避けて通れない重要なテーマです。APIからデータを取得する、ファイルを読み込む、タイマーで処理を遅延させる——こうした操作はすべて非同期処理で行われます。

この記事では、非同期処理の課題だったコールバック地獄から始まり、それを解決するPromise、さらにコードを読みやすくするasync/awaitまでを体系的に解説します。後半ではPromise.allなどの並列処理パターンや、fetch・リトライ・タイムアウトといった実務で頻出するパターンも紹介します。

この記事で学べること

  • コールバック地獄の問題点とPromiseによる解決
  • Promiseの3つの状態(pending / fulfilled / rejected)
  • async/awaitによる直感的な非同期処理の書き方
  • Promise.all / allSettled / race / any の使い分け
  • fetch・リトライ・タイムアウトなど実務パターン集
  • よくあるミスと注意点
スポンサーリンク

コールバック地獄からPromiseへ——なぜ非同期処理の仕組みが必要なのか

JavaScriptはシングルスレッドで動作する言語です。つまり、一度に1つの処理しか実行できません。もしAPIリクエストやファイル読み込みを同期的に行うと、その完了を待つ間、画面が完全にフリーズしてしまいます。

この問題を解決するために非同期処理が必要になります。初期のJavaScriptではコールバック関数を使って非同期処理を実現していました。

コールバック関数による非同期処理

コールバック関数とは、「処理が完了したら呼び出す関数」を引数として渡すパターンです。

コールバック関数の基本
// コールバック関数を使った非同期処理の例
function fetchUserData(userId, callback) {
  setTimeout(() => {
    const user = { id: userId, name: '田中太郎' };
    callback(user);
  }, 1000);
}

// 使用例
fetchUserData(1, (user) => {
  console.log(user.name); // 田中太郎
});

シンプルな処理ならコールバック関数で問題ありません。しかし、非同期処理が連鎖する場合に大きな問題が発生します。

コールバック地獄とは

非同期処理を順番に実行する必要がある場合、コールバック関数がどんどんネストしていきます。これがコールバック地獄(Callback Hell)です。

コールバック地獄の例
// ユーザー取得 → 注文取得 → 商品取得 → 在庫確認
getUser(userId, (user) => {
  getOrders(user.id, (orders) => {
    getProduct(orders[0].productId, (product) => {
      checkStock(product.id, (stock) => {
        console.log('在庫数:', stock);
        // さらにネストが続く...
      });
    });
  });
});

コールバック地獄の問題点:

  • ネストが深くなり、コードの可読性が著しく低下する
  • エラーハンドリングが複雑になる(各コールバックで個別に処理が必要)
  • 処理の流れを追いにくく、デバッグが困難
  • コードの再利用性が低い

この問題を解決するためにES2015(ES6)で導入されたのがPromiseです。

Promiseの基本——非同期処理を扱うオブジェクト

Promiseは、非同期処理の「最終的な完了(もしくは失敗)」を表すオブジェクトです。「今はまだ結果がわからないけど、いずれ結果が返ってくる」という約束(Promise)を表現しています。

Promiseの3つの状態

Promiseには以下の3つの状態があり、一度決まった状態は変わりません。

状態 英語名 意味
待機中 pending まだ処理が完了していない初期状態
履行済み fulfilled 処理が成功し、結果の値が確定した
拒否済み rejected 処理が失敗し、エラーの理由が確定した

ポイント:Promiseの状態は pending → fulfilled または pending → rejected のどちらか一方向にしか変化しません。一度 fulfilled や rejected になったら、もう変わることはありません。これを不変(immutable)と呼びます。

Promiseの作り方(new Promise)

Promiseは new Promise() コンストラクタで作成します。引数には resolve(成功時に呼ぶ関数)と reject(失敗時に呼ぶ関数)を受け取るコールバックを渡します。

Promiseの作成
const promise = new Promise((resolve, reject) => {
  // 非同期処理を実行
  const success = true;

  if (success) {
    resolve('処理が成功しました'); // fulfilled 状態にする
  } else {
    reject(new Error('処理が失敗しました')); // rejected 状態にする
  }
});

then / catch / finally チェーン

Promiseの結果を受け取るには、then()(成功時)、catch()(失敗時)、finally()(成功・失敗に関わらず実行)を使います。これらはチェーン(連鎖)して書けるのが大きな特徴です。

then / catch / finally
function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const data = { id: 1, title: 'JavaScript入門' };
      resolve(data);
    }, 1000);
  });
}

fetchData()
  .then((data) => {
    console.log('成功:', data.title);
    return data.id; // 次のthenに渡せる
  })
  .then((id) => {
    console.log('ID:', id);
  })
  .catch((error) => {
    console.error('エラー:', error.message);
  })
  .finally(() => {
    console.log('処理完了');
  });
実行結果
成功: JavaScript入門
ID: 1
処理完了

Promiseチェーンでコールバック地獄を解消

先ほどのコールバック地獄をPromiseで書き直すと、ネストが浅くなり読みやすくなります。

Promiseチェーンによる改善
// Promiseチェーンでフラットに書ける
getUser(userId)
  .then((user) => getOrders(user.id))
  .then((orders) => getProduct(orders[0].productId))
  .then((product) => checkStock(product.id))
  .then((stock) => {
    console.log('在庫数:', stock);
  })
  .catch((error) => {
    console.error('エラー:', error);
  });

Promiseチェーンのメリット:ネストが1段で済み、エラーハンドリングも末尾の catch() 1つでまとめて行えます。どこでエラーが発生しても catch に集約されるため、コードの見通しが格段に良くなります。

Promise.resolve() と Promise.reject()

すぐに結果が確定しているPromiseを簡単に作るための静的メソッドです。

Promise.resolve / Promise.reject
// 即座に fulfilled になるPromise
const resolved = Promise.resolve('成功値');
resolved.then((v) => console.log(v)); // '成功値'

// 即座に rejected になるPromise
const rejected = Promise.reject(new Error('失敗'));
rejected.catch((e) => console.error(e.message)); // '失敗'

// テストやキャッシュで便利
function getData(useCache) {
  if (useCache && cache) {
    return Promise.resolve(cache); // キャッシュがあれば即返す
  }
  return fetch('/api/data').then((r) => r.json());
}

async/awaitの基本——Promiseをもっと読みやすく

async/awaitはES2017で導入された構文で、Promiseベースの非同期処理を同期処理のように書くことができます。Promiseを置き換えるものではなく、Promiseの上に構築されたシンタックスシュガー(糖衣構文)です。

async関数の宣言

関数の前に async キーワードをつけると、その関数は常にPromiseを返すようになります。

async関数の宣言方法
// 関数宣言
async function fetchData() {
  return 'データ';
}

// 関数式
const fetchData = async function() {
  return 'データ';
};

// アロー関数
const fetchData = async () => {
  return 'データ';
};

// async関数は常にPromiseを返す
fetchData().then((result) => {
  console.log(result); // 'データ'
});

// メソッドにも使える
class Api {
  async getUser(id) {
    const res = await fetch(`/api/users/${id}`);
    return res.json();
  }
}

awaitでPromiseの結果を待つ

awaitasync関数の中でのみ使えるキーワードで、Promiseの結果が確定するまで処理を一時停止します。

awaitの基本的な使い方
async function main() {
  console.log('開始');

  // Promiseが解決されるまで待つ
  const result = await fetchData();
  console.log('結果:', result);

  // 前の処理が終わってから次が実行される
  const result2 = await fetchMore(result);
  console.log('結果2:', result2);

  console.log('完了');
}

main();

先ほどのコールバック地獄をasync/awaitで書き直すと、さらに自然な流れになります。

async/awaitで書き直したコード
async function checkProductStock(userId) {
  const user = await getUser(userId);
  const orders = await getOrders(user.id);
  const product = await getProduct(orders[0].productId);
  const stock = await checkStock(product.id);
  console.log('在庫数:', stock);
}

読みやすさの比較:コールバック地獄 → Promiseチェーン → async/await と進むにつれ、コードが「上から下に順番に読める」自然な構造になっていることがわかります。

try/catchでエラーハンドリング

async/awaitでは、通常のtry/catch文でエラーハンドリングができます。Promiseの .catch() よりも直感的です。

try/catchによるエラーハンドリング
async function fetchUserData() {
  try {
    const response = await fetch('/api/user');

    // レスポンスのステータスチェック
    if (!response.ok) {
      throw new Error(`HTTPエラー: ${response.status}`);
    }

    const data = await response.json();
    console.log('ユーザー:', data);
    return data;

  } catch (error) {
    // ネットワークエラー、HTTPエラー、JSONパースエラーなど
    console.error('データ取得に失敗:', error.message);
    return null;

  } finally {
    console.log('通信処理完了');
  }
}

トップレベルawait

ES2022以降、ESモジュールのトップレベル(関数の外)でも await が使えるようになりました。

トップレベルawait(ESモジュール)
// ESモジュール(.mjs または type="module")で使用可能

// async関数で囲まなくてOK
const response = await fetch('/api/config');
const config = await response.json();

console.log(config);

// 動的インポートとの組み合わせ
const module = await import('./utils.js');
module.doSomething();

注意:トップレベルawaitはESモジュール<script type="module">.mjs ファイル)でのみ使用可能です。通常のスクリプト(<script>タグ)やCommonJS(require)では使えません。

Promiseとasync/awaitの違い(比較表)

ここまでの内容を比較表でまとめます。

比較項目 Promise(then/catch) async/await
書き方 .then().catch() のチェーン 同期処理風に上から下へ
可読性 チェーンが長いとやや複雑 直感的で読みやすい
エラー処理 .catch() メソッド try/catch 文
デバッグ チェーン内のブレークポイントが使いにくい ステップ実行しやすい
並列処理 Promise.all() 等を直接使う await Promise.all() で組み合わせ
戻り値 Promise オブジェクト Promise オブジェクト(同じ)
導入時期 ES2015(ES6) ES2017(ES8)
使いどころ 並列処理、短い処理、イベントハンドラ 直列処理、複雑なロジック、メイン処理

結論:async/awaitはPromiseの上に構築された構文なので、どちらか一方だけを学べばいいというものではありません。Promiseの仕組みを理解した上でasync/awaitを使うのがベストプラクティスです。

同じ処理をPromiseとasync/awaitの両方で書いてみましょう。

Promise版
// Promise版: ユーザー情報を取得して表示
function displayUser(userId) {
  return fetch(`/api/users/${userId}`)
    .then((response) => {
      if (!response.ok) throw new Error('取得失敗');
      return response.json();
    })
    .then((user) => {
      console.log(`名前: ${user.name}`);
      return user;
    })
    .catch((error) => {
      console.error('エラー:', error.message);
    });
}
async/await版
// async/await版: 同じ処理をより読みやすく
async function displayUser(userId) {
  try {
    const response = await fetch(`/api/users/${userId}`);
    if (!response.ok) throw new Error('取得失敗');

    const user = await response.json();
    console.log(`名前: ${user.name}`);
    return user;

  } catch (error) {
    console.error('エラー:', error.message);
  }
}

並列処理のパターン——Promise.all / allSettled / race / any

複数の非同期処理を同時に実行したい場合、Promiseには4つの静的メソッドが用意されています。それぞれの挙動が異なるので、用途に応じて使い分けましょう。

メソッド 挙動 1つ失敗した場合 導入
Promise.all 全部成功を待つ 即座にreject ES2015
Promise.allSettled 全部完了を待つ 他も待ってから結果を返す ES2020
Promise.race 最初に完了した結果 失敗が最初ならreject ES2015
Promise.any 最初に成功した結果 無視して他の成功を待つ ES2021

Promise.all——全部成功を待つ

複数のPromiseを同時に実行し、すべてが成功したら結果を配列で返します。1つでも失敗すると即座にrejectされます。

Promise.all の使い方
// 3つのAPIを同時に呼び出す
async function fetchDashboardData() {
  try {
    const [user, posts, notifications] = await Promise.all([
      fetch('/api/user').then((r) => r.json()),
      fetch('/api/posts').then((r) => r.json()),
      fetch('/api/notifications').then((r) => r.json()),
    ]);

    console.log(user, posts, notifications);
  } catch (error) {
    // 1つでも失敗するとここに来る
    console.error('いずれかの取得に失敗:', error);
  }
}

パフォーマンスのメリット:3つのAPIリクエストを直列で実行すると合計3秒かかる処理が、Promise.allで並列実行すると最も遅い1つ分(約1秒)で完了します。独立した複数のリクエストは積極的にPromise.allを使いましょう。

Promise.allSettled——全部完了を待つ(成功・失敗を問わず)

すべてのPromiseが完了(成功 or 失敗)するまで待ち、各結果のステータスと値を返します。1つ失敗しても他の結果を取得できます。

Promise.allSettled の使い方
const results = await Promise.allSettled([
  fetch('/api/user').then((r) => r.json()),
  fetch('/api/invalid-endpoint'),  // これが失敗しても…
  fetch('/api/posts').then((r) => r.json()),
]);

// 各結果を個別にチェック
results.forEach((result, index) => {
  if (result.status === 'fulfilled') {
    console.log(`#${index} 成功:`, result.value);
  } else {
    console.error(`#${index} 失敗:`, result.reason);
  }
});
実行結果
#0 成功: { id: 1, name: "田中太郎" }
#1 失敗: TypeError: Failed to fetch
#2 成功: [{ id: 1, title: "記事1" }]

Promise.race——最も早く完了した結果を取得

複数のPromiseのうち、最初に完了(成功 or 失敗)したものの結果を返します。タイムアウト処理の実装に便利です。

Promise.race の使い方
// タイムアウト処理の実装例
function timeout(ms) {
  return new Promise((_, reject) => {
    setTimeout(() => reject(new Error('タイムアウト')), ms);
  });
}

try {
  const result = await Promise.race([
    fetch('/api/data').then((r) => r.json()),
    timeout(5000), // 5秒でタイムアウト
  ]);
  console.log('データ取得成功:', result);
} catch (error) {
  console.error(error.message); // 'タイムアウト'
}

Promise.any——最初に成功した結果を取得

複数のPromiseのうち、最初に成功したものの結果を返します。失敗は無視されます。すべて失敗した場合のみAggregateErrorがスローされます。

Promise.any の使い方
// 複数のCDNから最も速いものを使う
async function fetchFromFastestCDN() {
  try {
    const data = await Promise.any([
      fetch('https://cdn1.example.com/data.json').then((r) => r.json()),
      fetch('https://cdn2.example.com/data.json').then((r) => r.json()),
      fetch('https://cdn3.example.com/data.json').then((r) => r.json()),
    ]);
    console.log('最速のCDNから取得:', data);
  } catch (error) {
    // すべて失敗した場合のみ AggregateError
    console.error('全CDNで失敗:', error.errors);
  }
}

実務でよく使うパターン集

ここからは、実際の開発で頻繁に使われる非同期処理のパターンを紹介します。そのままコピペして使えるコード例を中心に解説します。

fetch APIでのデータ取得(GET / POST)

最も基本的なパターンです。fetchはPromiseを返すので、async/awaitと相性抜群です。

GETリクエスト
async function getUsers() {
  const response = await fetch('https://jsonplaceholder.typicode.com/users');

  if (!response.ok) {
    throw new Error(`HTTPエラー: ${response.status}`);
  }

  const users = await response.json();
  return users;
}
POSTリクエスト
async function createUser(userData) {
  const response = await fetch('https://jsonplaceholder.typicode.com/users', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(userData),
  });

  if (!response.ok) {
    throw new Error(`HTTPエラー: ${response.status}`);
  }

  return await response.json();
}

// 使用例
const newUser = await createUser({
  name: '田中太郎',
  email: 'tanaka@example.com',
});

注意:fetch() はネットワークエラー(サーバーに到達できない場合)でのみrejectされます。404や500などのHTTPエラーではrejectされないため、response.ok を必ずチェックしましょう。

複数APIの直列呼び出し

前の結果を使って次のリクエストを行うパターンです。async/awaitなら自然に書けます。

直列と並列を組み合わせたAPI呼び出し
async function getUserWithPosts(userId) {
  // 1. ユーザー情報を取得
  const userRes = await fetch(`/api/users/${userId}`);
  const user = await userRes.json();

  // 2. そのユーザーの投稿を取得(前の結果を使う)
  const postsRes = await fetch(`/api/users/${user.id}/posts`);
  const posts = await postsRes.json();

  // 3. 各投稿のコメント数を並列で取得
  const postsWithComments = await Promise.all(
    posts.map(async (post) => {
      const commentsRes = await fetch(`/api/posts/${post.id}/comments`);
      const comments = await commentsRes.json();
      return { ...post, commentCount: comments.length };
    })
  );

  return { ...user, posts: postsWithComments };
}

直列と並列の組み合わせ:上の例では、ユーザー取得→投稿取得は直列(前の結果が必要)、各投稿のコメント取得はPromise.allで並列(独立している)にしています。依存関係に応じて使い分けるのが実務のコツです。

リトライ処理

ネットワークが不安定な場合、失敗したリクエストを自動で再試行するパターンです。

リトライ処理の実装
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }

      return await response.json();

    } catch (error) {
      console.warn(`試行 ${attempt}/${maxRetries} 失敗:`, error.message);

      if (attempt === maxRetries) {
        throw new Error(
          `${maxRetries}回の再試行後も失敗: ${error.message}`
        );
      }

      // 指数バックオフ(1秒→2秒→4秒...)
      const delay = 1000 * 2 ** (attempt - 1);
      console.log(`${delay}ms後にリトライ...`);
      await new Promise((r) => setTimeout(r, delay));
    }
  }
}

// 使用例
const data = await fetchWithRetry('/api/unstable-endpoint', {}, 3);

指数バックオフとは:リトライ間隔を1秒→2秒→4秒のように指数的に増やす手法です。サーバーに過度な負荷をかけないための一般的なプラクティスです。

タイムアウト付きfetch

fetchにはデフォルトのタイムアウトがないため、自分で実装する必要があります。AbortControllerを使うのが推奨される方法です。

AbortControllerによるタイムアウト
async function fetchWithTimeout(url, timeoutMs = 5000) {
  const controller = new AbortController();

  // 指定時間後にリクエストを中断
  const timeoutId = setTimeout(() => {
    controller.abort();
  }, timeoutMs);

  try {
    const response = await fetch(url, {
      signal: controller.signal,
    });
    return await response.json();

  } catch (error) {
    if (error.name === 'AbortError') {
      throw new Error(`リクエストが${timeoutMs}msでタイムアウトしました`);
    }
    throw error;

  } finally {
    clearTimeout(timeoutId);
  }
}

// 使用例
try {
  const data = await fetchWithTimeout('/api/slow-endpoint', 3000);
  console.log(data);
} catch (error) {
  console.error(error.message);
}

ローディング表示の制御

非同期処理中にローディング状態を管理するパターンです。try/finallyを使うのがポイントです。

ローディング表示の制御
async function loadData() {
  const loadingEl = document.getElementById('loading');
  const contentEl = document.getElementById('content');
  const errorEl = document.getElementById('error');

  loadingEl.style.display = 'block';
  contentEl.style.display = 'none';
  errorEl.style.display = 'none';

  try {
    const data = await fetchWithRetry('/api/data');
    contentEl.textContent = JSON.stringify(data, null, 2);
    contentEl.style.display = 'block';

  } catch (error) {
    errorEl.textContent = error.message;
    errorEl.style.display = 'block';

  } finally {
    // 成功でも失敗でもローディングを非表示にする
    loadingEl.style.display = 'none';
  }
}

finallyの活用:finally ブロックは成功時も失敗時も必ず実行されるため、ローディング表示の解除に最適です。これを忘れると、エラー時にローディングが表示され続けるバグになります。

よくあるミスと注意点

非同期処理は便利ですが、ハマりやすいポイントがいくつかあります。実務で遭遇しがちなミスを事前に把握しておきましょう。

1. awaitの付け忘れ

最も多いミスです。awaitを付け忘れると、Promiseオブジェクトそのものが変数に入ります。

awaitの付け忘れ
async function example() {
  // NG: awaitを付け忘れている
  const data = fetch('/api/data');
  console.log(data); // Promise {<pending>} ← 値ではなくPromiseオブジェクト

  // OK: awaitを付ける
  const response = await fetch('/api/data');
  const data2 = await response.json();
  console.log(data2); // 実際のデータ
}

2. forループ内でのawait(直列になる問題)

forループ内でawaitを使うと、各イテレーションが直列実行になり、パフォーマンスが大幅に低下します。

forループ内のawait
const userIds = [1, 2, 3, 4, 5];

// NG: 直列実行(5秒かかる)
const users = [];
for (const id of userIds) {
  const user = await getUser(id); // 1つずつ順番に実行される
  users.push(user);
}

// OK: 並列実行(1秒で完了)
const users2 = await Promise.all(
  userIds.map((id) => getUser(id))
);

注意:ただし、前の結果を次のリクエストで使う場合は直列実行が正しい選択です。独立した処理にのみPromise.allを使いましょう。また、大量の並列リクエストはサーバーに負荷をかけるため、必要に応じて並列数を制限する仕組みも検討してください。

3. エラーの握りつぶし

catchブロックで何もしない、またはcatchを忘れると、エラーが見えなくなります。

エラーの握りつぶし
// NG: エラーを握りつぶしている
async function badExample() {
  try {
    const data = await fetch('/api/data');
  } catch (e) {
    // 何もしない → バグの原因が見つからなくなる
  }
}

// OK: 最低限ログを出す
async function goodExample() {
  try {
    const data = await fetch('/api/data');
  } catch (error) {
    console.error('APIリクエスト失敗:', error);
    // 必要に応じてリスロー、UIへの通知、フォールバック値の返却など
  }
}

// NG: Promiseのcatchなし → UnhandledPromiseRejection
fetch('/api/data').then((r) => r.json());
// ↑ エラー時に UnhandledPromiseRejection が発生

// OK: 必ずcatchを付ける
fetch('/api/data')
  .then((r) => r.json())
  .catch((error) => console.error(error));

4. async関数の戻り値は必ずPromise

async関数の中で return した値は自動的にPromiseでラップされます。呼び出し側で直接値を使うには await または .then() が必要です。

async関数の戻り値
async function getData() {
  return 'Hello'; // 自動的に Promise.resolve('Hello') になる
}

// NG: async関数の結果を直接使おうとする
const result = getData();
console.log(result); // Promise {<fulfilled>: 'Hello'}

// OK: awaitで結果を取り出す
const result2 = await getData();
console.log(result2); // 'Hello'

// OK: thenで結果を取り出す
getData().then((result3) => {
  console.log(result3); // 'Hello'
});

5. forEachではawaitが効かない

Array.forEachのコールバック内でawaitを使っても、forEachは完了を待ちません。

forEachとawaitの落とし穴
const urls = ['/api/a', '/api/b', '/api/c'];

// NG: forEachはawaitの完了を待たない
urls.forEach(async (url) => {
  const data = await fetch(url);
  console.log(data);
});
console.log('完了'); // fetchより先に実行される!

// OK: for...of を使う(直列)
for (const url of urls) {
  const data = await fetch(url);
  console.log(data);
}
console.log('完了'); // 全fetchの後に実行される

// OK: Promise.all + map を使う(並列)
await Promise.all(urls.map(async (url) => {
  const data = await fetch(url);
  console.log(data);
}));
console.log('完了'); // 全fetchの後に実行される

よくあるミスを表にまとめます。

ミスの内容 症状 対処法
awaitの付け忘れ 変数にPromiseが入る awaitを付ける
forループ内await 処理が遅い(直列実行) Promise.all + mapで並列化
エラーの握りつぶし 原因不明のバグ console.errorでログ出力
async関数の戻り値を直接使用 Promiseオブジェクトが表示される awaitまたは.then()で取り出す
forEachでawait 完了を待たずに次の処理が実行 for…ofまたはPromise.all + map
catchなしのPromise UnhandledPromiseRejection .catch()またはtry/catchを付ける

まとめ:Promiseとasync/awaitの使い分けフローチャート

最後に、どちらを使うべきか迷ったときの判断基準をまとめます。

シーン 推奨 理由
直列の非同期処理 async/await 上から下に読めて直感的
並列の非同期処理 Promise.all + await 並列実行にはPromise.allが必要
エラーハンドリングが複雑 async/await + try/catch 通常のtry/catchで書けて可読性が高い
短いPromiseチェーン then/catch 1〜2段なら簡潔に書ける
イベントハンドラ内 async/await asyncを付けるだけで使える
条件分岐が多いロジック async/await if/elseが自然に書ける

基本方針:迷ったらasync/awaitを使いましょう。可読性が高く、エラーハンドリングも直感的です。並列処理が必要な場面でのみ Promise.all を組み合わせ、短い処理には .then() を使う——これが現在のベストプラクティスです。

この記事のポイントを振り返ります。

この記事のまとめ

  • コールバック地獄を解決するためにPromiseが生まれ、さらにasync/awaitで読みやすくなった
  • Promiseにはpending / fulfilled / rejectedの3つの状態がある
  • async/awaitはPromiseのシンタックスシュガーで、同期処理のように書ける
  • 並列処理にはPromise.all(全成功)、allSettled(全完了)、race(最速)、any(最初の成功)を使い分ける
  • fetchのresponse.okチェック、リトライタイムアウトは実務で必須のパターン
  • awaitの付け忘れforループ内awaitエラーの握りつぶしに注意する
  • 迷ったらasync/awaitをベースにし、並列処理にはPromise.allを組み合わせる

非同期処理はJavaScriptの核心部分です。Promiseの仕組みをしっかり理解した上でasync/awaitを使いこなせば、複雑な非同期処理もシンプルに書けるようになります。