この記事で解決すること

「async/awaitって何?なんで必要なの?」

非同期処理の概念から、async/awaitの使い方まで段階的に解説します。

そもそも非同期処理とは

JavaScriptは基本的に「上から順番に実行」されます。でも、時間がかかる処理(API通信、ファイル読み込みなど)を待っている間、画面が固まったら困りますよね。

同期処理(待つ):
料理を注文 → 料理が来るまでボーッと待つ → 食べる

非同期処理(待たない):
料理を注文 → 待ってる間にスマホを見る → 料理が来たら食べる

非同期処理は「待っている間に他のことをする」仕組みです。Webアプリではfetch APIを使ったサーバーとの通信が代表的な非同期処理です。

歴史: なぜasync/awaitが生まれたか

時代1: コールバック(地獄)

// データを取得して、加工して、保存する
getData(function(data) {
  processData(data, function(processed) {
    saveData(processed, function(result) {
      console.log(result);
      // さらにネストが深くなる...
    });
  });
});

ネストが深くなり、読みにくい。これが「コールバック地獄」です。

時代2: Promise(チェーン)

getData()
  .then(data => processData(data))
  .then(processed => saveData(processed))
  .then(result => console.log(result))
  .catch(error => console.error(error));

だいぶ読みやすくなりました。でも .then() のチェーンが長くなると、まだ複雑です。

時代3: async/await(現在)

async function main() {
  try {
    const data = await getData();
    const processed = await processData(data);
    const result = await saveData(processed);
    console.log(result);
  } catch (error) {
    console.error(error);
  }
}

普通の同期処理のように読める!これがasync/awaitの最大のメリットです。JavaScriptの型安全性を高めたい場合は、TypeScriptを始めるべき理由もあわせて読んでみてください。

async/awaitの基本ルール

ルール1: awaitasync関数の中でしか使えない

// NG: asyncがない関数内でawaitは使えない
function fetchUser() {
  const response = await fetch('/api/user'); // エラー!
}

// OK: async を付ける
async function fetchUser() {
  const response = await fetch('/api/user'); // OK!
}

ルール2: awaitは Promiseの完了を待つ

async function example() {
  console.log('1: 開始');
  
  // fetchはPromiseを返す。awaitで完了を待つ
  const response = await fetch('https://api.example.com/data');
  
  console.log('2: データ取得完了');
  
  const data = await response.json();
  
  console.log('3: JSON変換完了', data);
}

実行順序は必ず 1 → 2 → 3 になります。

ルール3: async関数は必ずPromiseを返す

async function greet() {
  return 'こんにちは';
}

// 実際にはPromiseが返される
greet().then(message => console.log(message)); // こんにちは

実践例

例1: APIからデータを取得する

async function getWeather(city) {
  try {
    const response = await fetch(`https://api.weather.com/${city}`);
    
    if (!response.ok) {
      throw new Error(`HTTP error: ${response.status}`);
    }
    
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('天気の取得に失敗:', error.message);
    return null;
  }
}

// 使い方
const weather = await getWeather('tokyo');

例2: 複数のAPIを順番に呼ぶ

async function getUserPosts(userId) {
  // まずユーザー情報を取得
  const user = await fetch(`/api/users/${userId}`).then(r => r.json());
  
  // ユーザー情報をもとに投稿を取得
  const posts = await fetch(`/api/posts?author=${user.name}`).then(r => r.json());
  
  return { user, posts };
}

例3: 複数のAPIを同時に呼ぶ(Promise.all)

async function getDashboardData() {
  // 3つのAPIを同時に呼ぶ(待ち時間短縮)
  const [users, posts, comments] = await Promise.all([
    fetch('/api/users').then(r => r.json()),
    fetch('/api/posts').then(r => r.json()),
    fetch('/api/comments').then(r => r.json()),
  ]);

  return { users, posts, comments };
}

Promise.all を使うと、独立した複数の処理を並列実行できます。取得したデータを配列メソッドで加工する方法については、JavaScriptの配列メソッド入門 ― map・filter・reduceの使い方で解説しています。

よくあるエラーと対処法

エラー1: await is only valid in async functions

// NG
function main() {
  const data = await fetchData(); // エラー
}

// OK: asyncを付ける
async function main() {
  const data = await fetchData();
}

APIから取得するデータはJSON形式であることがほとんどです。JSONの基本を知っておくとデバッグがしやすくなります。

エラー2: awaitを付け忘れてPromiseオブジェクトが返る

async function example() {
  // NG: awaitがないのでPromiseオブジェクトが入る
  const data = fetch('/api/data');
  console.log(data); // Promise { <pending> }

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

よくある質問(FAQ)

Q: async/awaitとPromiseの.then()はどちらを使うべきですか?

A: 基本的にはasync/awaitが読みやすくておすすめです。ただし、Promise.all のように複数の非同期処理を並列実行する場合は、Promiseの機能と組み合わせて使います。どちらか一方ではなく、場面に応じて使い分けるのがベストです。

Q: await を付け忘れるとどうなりますか?

A: Promiseオブジェクトがそのまま変数に入ります。console.log すると Promise { <pending> } と表示されます。データが取れていないのに処理が進んでしまうので、バグの原因になります。

Q: async関数の中で return した値はどうなりますか?

A: 自動的にPromiseでラップされて返されます。呼び出し側では await を付けて受け取るか、.then() で値を取り出します。

Q: エラー処理は try/catch 以外の方法はありますか?

A: async関数の戻り値はPromiseなので、呼び出し側で .catch() を使うこともできます。ただし、関数内で完結させたい場合は try/catch の方が分かりやすいです。

Q: トップレベル(関数の外)で await は使えますか?

A: ES2022以降のモジュール(<script type="module"> やNode.jsのESモジュール)ではトップレベル await が使えます。通常のスクリプトでは使えないので、async 関数で囲む必要があります。

まとめ

  • 非同期処理は「待っている間に他のことをする」仕組み
  • asyncを関数に付けると、その中でawaitが使える
  • awaitはPromiseの完了を待って結果を返す
  • エラー処理はtry/catchで囲む
  • 独立した処理はPromise.allで並列実行すると速い

あわせて読みたい

関連リソース

JavaScriptをもっと学びたい方へ: