【JavaScript】処理を「待つ」構文!async/awaitの基本的な使い方と仕組み

JavaScriptでAPIからデータを取得したり、タイマー処理を行ったりする「非同期処理」。 以前の記事で、複数の非同期処理を並列で待つ Promise.all() をご紹介しましたが、今回はそもそもの基礎となる async / await について解説します!

関連記事

ひと昔前は .then() を繋げて書く「Promiseチェーン」という手法が主流でしたが、コードがネスト(階層化)しやすく、複雑になりがちでした。 しかし、async/await を使えば、まるで上から下へ順番に実行される同期処理(普通のコード)のようなスッキリとした見た目で非同期処理を書くことができます!

今回は「処理A → 処理B → 処理C」を順番に実行するデモを使って、従来の方法とのコードの違いを比較してみましょう!

デモ動作

See the Pen async/awaitの基本と仕組み by mikanbako (@mikanbako) on CodePen.

全画面でのデモ動作は以下のリンクからご確認下さい!(別タブで開かれます)

実装コード紹介

【HTML】

<div class="l-wrap">
  <h2>非同期処理 比較デモ</h2>
  <p class="p-desc">ボタンを押すと、1秒ごとに処理が進みます。コードの書き方の違いに注目してください!</p>
  
  <div class="p-demo-box">
    
    <!-- ❌ 従来の方法(.thenチェーン) -->
    <div class="p-card">
      <h3 class="p-card__title">従来の方法(.then() を使用)</h3>
      <button class="c-btn js-btn-old">.then() で実行</button>
      <div class="c-log-box js-log-old">ここに結果が出ます</div>
    </div>

    <!-- ⭕️ async/await を使用した方法 -->
    <div class="p-card">
      <h3 class="p-card__title">async/await を使用した方法</h3>
      <button class="c-btn c-btn--new js-btn-new">async/await で実行</button>
      <div class="c-log-box js-log-new">ここに結果が出ます</div>
    </div>

  </div>
</div>

【CSS】

/* ベース装飾 */
.l-wrap { 
  max-width: 800px;
  margin: 0 auto;
  padding: 40px 20px;
  color: #3e3e3e;
  text-align: center;
}
h2 { 
  font-size: 16px;
  color: #1bb4d4;
  margin-bottom: 16px;
}
.p-desc { 
  font-size: 16px;
  font-weight: bold;
  color: #666;
  line-height: 1.6;
  margin-bottom: 40px;
}

/* レイアウト調整 */
.p-demo-box {
  display: flex;
  flex-direction: column;
  gap: 30px;
  background: #fff;
  padding: 40px;
  border-radius: 12px;
  box-shadow: 0 4px 15px rgba(0,0,0,0.05);
}
.p-card { 
  border-bottom: 1px dashed #ccc;
  padding-bottom: 30px;
}
.p-card:last-of-type { 
  border-bottom: none;
  padding-bottom: 0;
}
.p-card__title { 
  font-size: 16px;
  font-weight: bold;
  margin-bottom: 16px;
  color: #333;
}

/* ボタンとログ画面 */
.c-btn {
  padding: 12px 30px;
  font-size: 16px;
  font-weight: bold;
  color: #fff;
  background-color: #999; /* 古い方はグレー */
  border-radius: 6px;
  cursor: pointer;
  margin-bottom: 16px;
}
.c-btn--new { 
  background-color: #e83e8c; /* 新しい方はピンク */
}

.c-log-box {
  background: #222;
  color: #0f0;
  padding: 16px;
  border-radius: 6px;
  min-height: 120px;
  text-align: left;
  font-size: 13px;
  font-family: monospace;
  white-space: pre-wrap; /* 改行を反映 */
}

【JavaScript】

// =========================================
// 準備:擬似的な非同期処理(API通信などを想定)
// 1秒(1000ms)待ってから文字列を返す関数
// =========================================
const dummyFetch = (taskName) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(`[${taskName}] 完了しました!`);
    }, 1000);
  });
};


// =========================================
// ❌ 1. 従来の方法(.then() チェーン)
// =========================================
const btnOld = document.querySelector('.js-btn-old');
const logOld = document.querySelector('.js-log-old');

btnOld.addEventListener('click', () => {
  logOld.textContent = "実行開始...\n";
  
  // .then() を繋げて書くため、ネストが深くなり読みにくい
  dummyFetch("処理A").then((resultA) => {
    logOld.textContent += resultA + "\n";
    
    return dummyFetch("処理B");
  }).then((resultB) => {
    logOld.textContent += resultB + "\n";
    
    return dummyFetch("処理C");
  }).then((resultC) => {
    logOld.textContent += resultC + "\n";
    logOld.textContent += "すべて完了!";
  });
});


// =========================================
// ⭕️ 2. async / awaitの方法
// =========================================
const btnNew = document.querySelector('.js-btn-new');
const logNew = document.querySelector('.js-log-new');

// 💡ポイント1: 関数に「async」をつける!
btnNew.addEventListener('click', async () => {
  logNew.textContent = "実行開始...\n";
  
  // 💡ポイント2: Promiseを返す関数の前に「await」をつける!
  // たったこれだけで、処理が終わるまで「待って」くれます。
  const resultA = await dummyFetch("処理A");
  logNew.textContent += resultA + "\n";
  
  const resultB = await dummyFetch("処理B");
  logNew.textContent += resultB + "\n";
  
  const resultC = await dummyFetch("処理C");
  logNew.textContent += resultC + "\n";
  
  logNew.textContent += "すべて完了!";
});

コードの解説・仕組み

.then() は直感的ではない?

btnOld.addEventListener('click', () => {
  logOld.textContent = "実行開始...\n";
  
  // .then() を繋げて書くため、ネストが深くなり読みにくい
  dummyFetch("処理A").then((resultA) => {
    logOld.textContent += resultA + "\n";
    
    return dummyFetch("処理B");
  }).then((resultB) => {
    logOld.textContent += resultB + "\n";
    
    return dummyFetch("処理C");
  }).then((resultC) => {
    logOld.textContent += resultC + "\n";
    logOld.textContent += "すべて完了!";
  });
});

従来の方法のコードを見ると、.then() の中にさらにコールバック関数(アロー関数)が入り込んでいるのがわかります。この書き方は、処理が長く複雑になればなるほど「今どの処理を行っているのか」が分かりづらくなる欠点がありました。

async / await のメリット

// 💡ポイント1: 関数に「async」をつける!
btnNew.addEventListener('click', async () => {
  logNew.textContent = "実行開始...\n";
  
  // 💡ポイント2: Promiseを返す関数の前に「await」をつける!
  // たったこれだけで、処理が終わるまで「待って」くれます。
  const resultA = await dummyFetch("処理A");
  logNew.textContent += resultA + "\n";
  
  const resultB = await dummyFetch("処理B");
  logNew.textContent += resultB + "\n";
  
  const resultC = await dummyFetch("処理C");
  logNew.textContent += resultC + "\n";
  
  logNew.textContent += "すべて完了!";
});

一方、async / await のコードはまるで 「上から下へ普通に実行しているだけ」 のように見えます!

  • 非同期処理を含ませたい関数に async をつける。
  • 待ってほしい処理(Promise)の前に await をつける。

たったこれだけのルールで、await を書いた行で処理が一時停止し、その結果(resolve された値)が返ってくるまで次の行に進みません。 非同期処理を、人間にとって一番読みやすい「同期処理のような見た目」で書けるのが async / await の魅力です!

補足:公式ドキュメント

より詳細な仕様や他の活用例について知りたい方は、ぜひMDNの公式ドキュメントもチェックしてみてください!

・MDN: async function
・MDN: await

まとめ

今回は、非同期処理の基本である async / await の使い方を解説しました。

現代のフロントエンド開発において、APIからのデータ取得(fetch など)でこの構文を使わない日はありません。 複数の処理を直列(順番)で待ちたい時は今回の await を。並列(同時)で待ちたい時は以前の記事で紹介した Promise.all() を使うなど、状況に合わせて非同期処理をマスターしていきましょう!

Prev
Next