知っておくべきJavaScript async/awaitパターン

Frontend2026-09-20TryQuickToolBox

非同期プログラミングは現代のJavaScriptの中核であり、async/awaitによってコードははるかに読みやすくなりました。しかし、多くの開発者が依然としてよくある落とし穴に悩まされています。処理されないPromiseの拒否、遅い順次リクエスト、混乱を招くエラーハンドリングなどです。この記事では、よりクリーンで効率的なコードをすぐに書けるようになる実践的なasync/awaitパターンを紹介します。

1. 基本的なasync/awaitの構文

async関数は常にPromiseを返します。awaitキーワードはPromiseが解決するまで実行を一時停止し、解決された値を返します。Promiseが拒否された場合、エラーをスローし、try/catchでキャッチできます。

async function fetchData() {
  try {
    const response = await fetch('/api/data');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Fetch failed:', error);
  }
}

これが基本です。しかし、実際のシナリオではより細やかなアプローチが必要になることがよくあります。

2. エラーを優雅に処理する

async/awaitの最大の利点の1つは、同期エラーと非同期エラーの両方にtry/catchを使用できることです。ただし、単一のtryブロック内で複数のawait呼び出しを行う場合は注意が必要です。最初の呼び出しが失敗すると、後続の呼び出しは実行されません。

より細かい制御が必要な場合は、各awaitを独自のtry/catchでラップするか、ヘルパー関数を使用して繰り返しのtry/catchブロックを避けることができます。

async function getUser(id) {
  try {
    const user = await fetchUser(id);
    return user;
  } catch (error) {
    console.error('Failed to fetch user:', error);
    throw error;
  }
}

3. Promiseを並列で実行する

複数の独立したPromiseを順次awaitするのは、よくあるパフォーマンス上の間違いです。複数のソースから結果を取得する必要がある場合は、Promise.all()を使用して並行して実行します。

// Sequential: slow
const user = await fetchUser();
const posts = await fetchPosts();

// Parallel: fast
const [user, posts] = await Promise.all([fetchUser(), fetchPosts()]);

すべてのPromiseが解決または拒否されるのを待ちたい場合は、Promise.allSettled()を使用します。これは、一部が失敗しても結果を収集したい場合に便利です。

4. 順序が重要な場合の順次実行

各ステップが前のステップに依存する場合など、項目を順番に処理する必要があることがあります。その場合は、forEach(非同期コールバックを待機しない)ではなく、awaitを伴うfor...ofループを使用します。

for (const item of items) {
  await processItem(item);
}

並行処理の数を制限する必要がある場合は、p-limitなどのライブラリを検討するか、簡単なキューを実装してください。

5. ループ内でのasync/awaitの落とし穴を避ける

Array.prototype.forEach内でawaitを使用するのは典型的な間違いです。forEachは非同期コールバックを待機しないため、Promiseが解決する前にループが終了します。常にfor...ofまたはPromise.allを伴うmapを使用してください。

6. タイムアウトを伴うasync/await

リクエストのハングを防ぐために、Promiseをタイムアウトと競争させることができます。

function timeout(ms) {
  return new Promise((_, reject) =>
    setTimeout(() => reject(new Error('Timeout')), ms)
  );
}

await Promise.race([fetchData(), timeout(5000)]);

このパターンは、ハングする可能性のあるAPI呼び出しに便利です。

7. 非同期ジェネレーターとfor await...of

非同期ジェネレーターを使用すると、値のストリームを非同期的に生成できます。for await...ofで消費できます。

async function* fetchPages() {
  let page = 1;
  while (true) {
    const data = await fetch(`/api/items?page=${page}`);
    if (data.length === 0) break;
    yield data;
    page++;
  }
}

for await (const page of fetchPages()) {
  console.log(page);
}

これは、ページネーションされたAPIやストリーミングデータに最適です。

8. よくある間違いとその回避方法

9. クリーンな非同期コードのベストプラクティス

FAQ

async/awaitとPromiseの違いは何ですか?

async/awaitはPromiseのシンタックスシュガーであり、非同期コードを同期的に見せます。チェーンとtry/catchによるエラーハンドリングを簡素化します。

forEachループ内でawaitを使用できますか?

いいえ、forEachは非同期コールバックを待機しません。代わりにfor...ofまたはPromise.allを伴うmapを使用してください。

複数の非同期操作を並列で実行するにはどうすればよいですか?

Promise.all()を使用して並行して実行し、すべてが解決するのを待ちます。

JSON APIを扱う際は、JSON Formatterを使用してレスポンスをすばやくフォーマットおよび検証できます。