組み込み関数

JavaScriptのasync/await入門|非同期処理をわかりやすく書く

async/awaitは、Promiseベースの非同期処理を同期的なコードのように書ける構文です。ES2017で導入され、コールバック地獄やPromiseチェーンの複雑さを解消します。API通信やファイル操作など非同期処理が必要な場面で必須の知識です。

基本的な使い方

asyncキーワードを関数に付けると、その関数は常にPromiseを返します。awaitはPromiseの完了を待ち、結果の値を返します。awaitasync関数の中でのみ使えます。

JavaScript
// async関数の基本
async function greet() {
  return "こんにちは";
}
greet().then(msg => console.log(msg));

// awaitでPromiseの完了を待つ
function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function example() {
  console.log("開始");
  await delay(1000);
  console.log("1秒後");
  await delay(1000);
  console.log("2秒後");
  return "完了";
}

example().then(result => console.log(result));
実行結果
こんにちは
開始
// 1秒後:
1秒後
// さらに1秒後:
2秒後
完了

awaitはPromiseが解決されるまで関数の実行を一時停止しますが、JavaScriptのイベントループはブロックしません。他の処理は並行して実行されます。

エラーハンドリング

async/awaitではtry/catchを使ってエラーを処理します。Promiseチェーンの.catch()よりも直感的に書けます。

JavaScript
// try/catchによるエラーハンドリング
async function fetchUser(id) {
  try {
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/users/${id}`
    );
    if (!response.ok) {
      throw new Error(`ユーザーが見つかりません (HTTP ${response.status})`);
    }
    const user = await response.json();
    console.log("取得成功:", user.name);
    return user;
  } catch (error) {
    console.error("エラー:", error.message);
    return null;
  } finally {
    console.log("処理完了");
  }
}

// Promiseチェーンとの比較
// Promise版
function fetchUserPromise(id) {
  return fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
    .then(response => {
      if (!response.ok) throw new Error("Not found");
      return response.json();
    })
    .then(user => {
      console.log(user.name);
      return user;
    })
    .catch(error => {
      console.error(error.message);
      return null;
    });
}
// async/await版の方がフラットで読みやすい
実行結果
取得成功: Leanne Graham
処理完了

並列実行と直列実行

複数の非同期処理を効率的に実行するために、Promise.all()Promise.allSettled()と組み合わせます。

JavaScript
function delay(ms, value) {
  return new Promise(resolve => setTimeout(() => resolve(value), ms));
}

// 直列実行(1つずつ順番に)- 合計3秒
async function sequential() {
  console.time("直列");
  const a = await delay(1000, "A");
  const b = await delay(1000, "B");
  const c = await delay(1000, "C");
  console.timeEnd("直列");
  console.log(a, b, c);
}

// 並列実行(同時に開始)- 合計1秒
async function parallel() {
  console.time("並列");
  const [a, b, c] = await Promise.all([
    delay(1000, "A"),
    delay(1000, "B"),
    delay(1000, "C")
  ]);
  console.timeEnd("並列");
  console.log(a, b, c);
}

// Promise.allSettled(失敗しても全結果を取得)
async function allSettledExample() {
  const results = await Promise.allSettled([
    delay(100, "成功1"),
    Promise.reject(new Error("失敗")),
    delay(100, "成功2")
  ]);
  results.forEach(r => {
    if (r.status === "fulfilled") {
      console.log("成功:", r.value);
    } else {
      console.log("失敗:", r.reason.message);
    }
  });
}
実行結果
直列: 3001ms
A B C
並列: 1002ms
A B C
成功: 成功1
失敗: 失敗
成功: 成功2
トップレベルawait

ES2022以降のモジュール(type="module")では、関数の外でもawaitが使えます。これにより、モジュールの初期化時に非同期処理を簡潔に書けるようになりました。

forEachでawaitは使えない

forEach()の中でawaitを使っても、各繰り返しは待機されません。順次実行したい場合はfor...ofループを使い、並列実行したい場合はPromise.all()map()を組み合わせてください。

まとめ

  • async関数は常にPromiseを返す
  • awaitはPromiseの解決を待って値を返す
  • エラーハンドリングはtry/catchで行う
  • 並列実行にはPromise.all()を使う
  • forEachではawaitが効かないためfor...ofを使う