JavaScriptの非同期処理|Promise・async/awaitの違い

プログラミング言語

JavaScriptで非同期処理を書く際、Promise・async/awaitの2つの書き方があります。両者は同じ仕組みの上に成り立つ関係ですが、書き心地・エラー処理・並列実行の見え方が異なります。本記事では、両者の違いと使い分けを解説します。

Promiseとは

「未来に結果が決まる値」を表すオブジェクトです。状態は pending → fulfilled or rejected の3つ。.then() .catch()でつなげて処理を書きます。

fetch('/api/user')
  .then((res) => res.json())
  .then((user) => console.log(user.name))
  .catch((err) => console.error(err));

async/awaitとは

Promiseを同期処理のように書ける構文です。async関数内でawaitを使うとPromiseの解決まで待ち、結果を返り値のように扱えます。

async function loadUser() {
  try {
    const res = await fetch('/api/user');
    const user = await res.json();
    console.log(user.name);
  } catch (err) {
    console.error(err);
  }
}

両者の関係

async関数は常にPromiseを返します。await xx.then(...)と本質的に同じです。async/awaitはPromiseの「糖衣構文(シンタックスシュガー)」と理解すると簡単です。

項目別比較

  • 可読性:直列処理はasync/awaitが圧倒的に読みやすい
  • エラー処理:async/awaitはtry-catch、Promiseは.catch
  • 並列処理:Promise.all/race/allSettled/anyを使う
  • 互換性:両者は同じPromiseオブジェクトに依存

並列実行のコツ

// 直列(遅い)
const a = await fetchA();
const b = await fetchB();

// 並列(速い)
const [a, b] = await Promise.all([fetchA(), fetchB()]);

// 失敗を許容して全部待つ
const results = await Promise.allSettled([fetchA(), fetchB()]);

よくある落とし穴

  • forEachの中でawaitしても並列化されない(mapで配列にしてPromise.all)
  • awaitを忘れるとPromiseのまま返る → 型エラーで気付くケースが多い
  • 非同期で発生したエラーは外のtry-catchでは捕捉できない
  • トップレベルawaitはESM環境でのみ使える

まとめ

Promiseとasync/awaitは「同じものを別の書き方で表現する」関係です。普段はasync/awaitで読みやすく、並列処理は Promise.all で速くを基本に書けば、ほとんどの非同期処理はシンプルに表現できます。