JavaScriptのasync/awaitとは?Promiseとの違いと使い方を解説

JavaScript

JavaScriptの「非同期処理」を学ぶ上で、避けて通れない最終到達点がasync/awaitです。

「Promiseの.then( )が増えすぎてコードが読みにくい」「非同期処理の実行順序をスマートに制御したい」といった課題をすべて解決してくれるのが、この構文です。

現在のフロントエンド開発(React、Vue.js、Next.jsなど)やNode.jsによるバックエンド開発において、API通信のほとんどはasync/awaitで記述されています。難しそうな名前に見えますが、実はPromiseの仕組みを理解していれば、async/awaitは決して難しくありません。 本記事で基本から実務レベルの使い方までマスターしていきましょう。

async/awaitとは?

まずは、この構文が持つ役割の結論からお伝えします。

結論:async/awaitの正体

async/awaitとは、「複雑になりがちなPromise(非同期処理)のコードを、まるで上から下へ順番に実行される同期処理のように、スッキリと読みやすく書くための構文」です。

完全に新しい仕組みではなく、内部的にはPromiseをベースにして動いています。つまり、Promiseという強力なエンジンを、より扱いやすい洗練されたデザイン(構文)で包み込んだもの(シンタックスシュガー)と言えます。

現在のJavaScript開発において非同期処理を書く場合、特別な理由がない限りはasync/awaitを使うのが業界の標準となっています。

なぜasync/awaitが必要なのか?

async/awaitが登場した背景には、従来の「Promise」が抱えていた記述上の課題(弱点)がありました。

Promiseは便利だがネストが増えやすい

Promiseの登場により、コールバック地獄は改善されました。しかし、複数の非同期処理を順番に行おうとすると、.then( )が連続で続き、コードの横幅や縦幅が広がって可読性が落ちるという問題が発生します。

// Promiseによる連続した非同期処理
fetchUserData()
  .then(user => fetchUserPosts(user.id))
  .then(posts => fetchPostComments(posts[0].id))
  .then(comments => {
    console.log(comments);
  });

一見綺麗ですが、処理が増えるたびに.then( )が無限に続き、どこで何のデータが引き継がれているのかを視覚的に追いかけるのが難しくなります。

async/awaitなら同期処理のように書ける

全く同じ処理を、async/awaitを使って書き換えてみます。

// async/awaitによる連続した非同期処理
const user = await fetchUserData();
const posts = await fetchUserPosts(user.id);
const comments = await fetchPostComments(posts[0].id);
console.log(comments);

いかがでしょうか。非同期処理であるにもかかわらず、.then( )もコールバック関数も一切登場しません。「データを取得して、変数に入れて、次の関数に渡す」という一連の流れが、上から下への一本道(同期処理と同じ見た目)で書けるようになります。これが、async/awaitが圧倒的に支持されている理由です。

asyncとawaitの役割

asyncとawaitは、必ず2つセット(ペア)で協力な効果を発揮します。それぞれの役割を分解して理解しましょう。

asyncとは?(非同期関数の宣言)

関数の前につけるキーワードです。asyncがついた関数は、「自動的にPromiseを返す関数(非同期関数)」へと変化します。

async function getMessage() {
  return "Hello";
}

// 実行すると、ただの文字列ではなく「Promiseオブジェクト」が返ってくる
console.log(getMessage()); // Promise { 'Hello' }

awaitとは?(処理の待機指示)

Promiseの完了を「待つ」キーワードです。awaitをプロパティや関数の前につけると、その非同期処理が完了して結果(データ)が返ってくるまで、次の行の実行を一時停止します。

  • 超重要ルール:awaitは、asyncをつけた関数の中でのみ使用できるという厳格な決まりがあります。

async/awaitの基本的な使い方

概念が分かったところで、具体的な基本構文とサンプルコードを見ていきましょう。

基本構文

async function 関数名() {
  const 結果 = await 非同期処理を返す関数();
  // 完了した後の処理
}

簡単なサンプル:タイマー処理の待機

実務のAPI通信を見る前に、イメージしやすいように「1秒待ってからログを出す」簡易的なサンプルで動きを確認します。

// 1秒後に解決するPromiseを返す関数
const delay = () => new Promise(resolve => setTimeout(resolve, 1000));

async function runTest() {
  console.log("スタート");
  
  await delay(); // ここで1秒間、処理がピタッと止まる
  
  console.log("1秒経ちました");
}

runTest();

実行すると、「スタート」が出力された後、正確に1秒のウェイト(待機)を挟んでから「1秒経ちました」が出力されます。

Promiseとの違い

「Promise」と「async/await」のどちらを使うべきか、実務的な観点から比較します。

Promiseの場合

  • 利点:処理を細かく分岐させたり、複数の非同期処理を同時に並行実行する(Promise.allなど)際に直感的。
  • 欠点:チェーンが長くなるとスコープ(変数の有効範囲)の管理が難しくなる。

async/awaitの場合

  • 利点:圧倒的にコードが読みやすい。変数を通常の同期処理と同じようにスコープ内で自由に扱える。
  • 欠点:ループ処理(forEachなど)の中で使う際に、挙動の注意が必要。

結論:どちらを使うべき?

現在の実務Web開発では、async/awaitが圧倒的多数派(主流)です。
コードの読みやすさはチーム開発におけるメンテナンス性に直結するため、特別な理由がない限りasync/awaitをベースに設計します。ただし、内部はPromiseですので、並列処理を行うPromise.all()などと組み合わせて使うのがエンジニアの標準的な書き方です。

エラー処理はtry…catchを使う

非同期処理において、ネットワークエラーやサーバー障害などの「エラーハンドリング」は必須です。Promiseでは.catch()を使いましたが、async/awaitではJavaScript標準のtry. . .catch構文を使用します。

なぜ必要なのか?

async/awaitはエラーの発生も「同期処理」と同じように扱いします。try. . .catchで囲っておかないと、非同期処理が失敗(Reject)した際にエラーをキャッチできず、最悪の場合アプリ全体がクラッシュしてしまいます。

基本的な書き方

async function loadData() {
  try {
    // 成功するかもしれない処理
    const data = await fetchUserData();
    console.log("成功:", data);
  } catch (error) {
    // エラーが発生した場合、ここにジャンプする
    console.error("エラーが発生しました:", error);
  }
}

正常系とtryの中に一本道で書き、異常系をcatchに集約できるため、コードの構造が非常にスッキリします。

async/awaitでよくある間違い

実務で頻出する、3つの代表的なアンチパターンを紹介します。

awaitをasyncの外で使ってしまう(最頻出)

// 誤った例
function initialize() {
  const data = await fetchData(); // エラー:SyntaxError: awaitはasync関数の中でしか使えません
}

awaitを使う場合は、必ずそのコードを包んでいる親関数にasyncを付与してください。

awaitの付け忘れ

async function getUser() {
  const user = fetchUserData(); // awaitの付け忘れ
  console.log(user.name); // エラー:userの中身はデータではなく「Promiseオブジェクト」になっているため
}

awaitを忘れると、データの取得完了を待たずに次の行に進んでしまうため、中身がただの空のPromiseオブジェクトになり、続く処理がすべて失敗します。

エラー処理(try…catch)を書かない

API通信は「いつでも100%成功する」とは限りません。ユーザーの電波状況が悪いだけで失敗します。try. . .catchのないasync/awaitは、いつ爆発するか分からない爆弾をコード内に放置しているのと同じです。必ずセットで記述しましょう。

実務でよく使うasync/awaitの例:fetchとの関係

実務のフロントエンド開発で最も頻出する、ブラウザ標準の通信機能fetchを使った具体的なデータ取得のパターンです。fetch関数は標準で「Promiseを返す」仕様になっているため、async/awaitと抜群の相性を誇ります。

// サーバーから商品データを取得して画面に表示する関数
async function displayProducts() {
  const apiUrl = "https://api.example.com/products";
  
  try {
    // 1. サーバーへ通信を開始し、レスポンスが返ってくるのを待つ
    const response = await fetch(apiUrl);
    
    // 通信自体は成功したが、サーバー側でエラー(404など)が発生していないかチェック
    if (!response.ok) {
      throw new Error("ネットワーク応答が正しくありません");
    }
    
    // 2. レスポンスデータをJSON形式に解析(パース)するのを待つ
    const products = await response.json();
    
    // 3. 届いたデータを安全に使用
    console.log("商品一覧の取得に成功しました:", products);
    
  } catch (error) {
    console.error("データ取得のフェーズで失敗:", error);
  }
}

実務では、このように「通信(fetch)」と「JSONへの変換(.json( ) )」の2段階でawaitを重ねる書き方が基本形となります。

まとめ:async/awaitを自分のものにする

async/awaitは、JavaScriptの非同期処理を極めてエレガントに解決してくれる協力な武器です。

  • async/awaitは、Promiseを同期処理のように読みやすく書く方法
  • asyncをつけた関数の中でのみ、待機を意味する“await` が使える
  • エラーハンドリングは、JavaScript標準のtry…catchを使用する
  • 実務(API通信、フォーム送信など)ではほぼ100%この構文が使われる

非同期処理のマスターへの一番の近道は、過去に自分が書いた、あるいはサンプルコードにある「Promiseの.then( )で書かれたコード」を、async/awaitを使って自分で書き換える練習をしてみることです。

頭の中でデータの流れ(タイムライン)が一本道に繋がった瞬間、モダンJavaScriptを使った開発が驚くほど楽しく、そしてスムーズに感じられるようになるはずです。さっそくエディタを開いて、シンプルな書き換えから挑戦してみましょう!

参考リンク

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