1. JavaScript

【JavaScript】非同期処理とは?Promiseとasync/awaitの基本を解説

2025.06.17

Share

JavaScriptでは、時間がかかる処理(API通信・ファイル読み込み・タイマーなど)を実行している間も、他のコードを止めずに処理を進める非同期処理が重要な役割を果たします。

本記事では、JavaScriptの非同期処理の基本と、Promiseasync/awaitの使い方を解説します。

非同期処理とは?

非同期処理(Asynchronous Processing)とは、ある処理が完了するのを待たずに、次の命令を先に実行していく処理方法のことです。

例えば:

console.log("処理開始");

setTimeout(() => {
  console.log("2秒後の処理");
}, 2000);

console.log("処理終了");

出力結果:

処理開始
処理終了
2秒後の処理

このように、setTimeoutによる処理は後回しにされ、次のコードが先に実行されます。

コールバック関数とその課題

非同期処理は、もともとコールバック関数で対応していました。

function getData(callback) {
  setTimeout(() => {
    callback("データを取得しました");
  }, 1000);
}

getData((data) => {
  console.log(data);
});

しかし、コールバックがネストしていくと、いわゆる「コールバック地獄」になり、コードが読みにくくなります。

Promiseとは?

Promiseは、非同期処理の結果を表すオブジェクトで、コールバック地獄を回避するために導入されました。

function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("データ取得成功");
    }, 1000);
  });
}

fetchData().then((result) => {
  console.log(result);
});

状態(state)

Promiseは3つの状態を持ちます:

  • pending(保留中)
  • fulfilled(成功)
  • rejected(失敗)

catchでエラーハンドリング

fetchData()
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
    console.error("エラー:", error);
  });

async/awaitとは?

asyncawaitは、Promiseをより同期処理のように書ける構文です。読みやすさと保守性が大幅に向上します。

async function showData() {
  try {
    const result = await fetchData();
    console.log(result);
  } catch (error) {
    console.error("エラー:", error);
  }
}

showData();

ポイント

  • asyncを付けた関数は、必ずPromiseを返す
  • awaitは、Promiseの結果を待つ(成功なら結果、失敗ならエラーを投げる)

Promiseとasync/awaitの使い分け

特徴Promiseasync/await
読みやすさ中程度(then/catchのネストあり)高い(直列に書ける)
エラーハンドリング.catch()を使用try/catchで対応
並列処理Promise.allなどを使用await Promise.all()で書ける

実践例:API通信

async function fetchUser() {
  const res = await fetch("https://jsonplaceholder.typicode.com/users/1");
  const user = await res.json();
  console.log(user.name);
}

fetchUser();

非同期処理の注意点

非同期処理は非常に便利ですが、以下の点に注意しないと思わぬバグや挙動に繋がります。

1. 処理の順序が保証されない

非同期処理は処理の完了順に従って実行されるため、コードの記述順とは異なる結果になることがあります。

fetchData1();
fetchData2(); // こちらが先に完了する可能性がある

→ 処理順が重要な場合は awaitPromise.then() を使って制御しましょう。

2. エラーハンドリングを忘れがち

非同期処理では、エラーが起きても即座にスローされず、見落としやすいです。try/catchcatchメソッドで必ず対応を。

// NG: エラーが握りつぶされる
const res = await fetch(url);

// OK: try-catchで囲む
try {
  const res = await fetch(url);
} catch (e) {
  console.error("通信に失敗しました", e);
}

3. awaitはforEachでは使えない

Array.prototype.forEach()内でawaitを使っても、非同期処理を正しく待ってくれません。代わりにfor...ofを使いましょう。

// ❌ 意図通りに動作しない
array.forEach(async (item) => {
  await doSomething(item);
});

// ✅ こっちを使う
for (const item of array) {
  await doSomething(item);
}

4. 並列処理と直列処理を正しく使い分ける

すべてをawaitで直列に処理すると、処理速度が落ちることがあります。独立した処理Promise.all() で並列に行うと高速です。

// 直列(遅い)
await task1();
await task2();

// 並列(速い)
await Promise.all([task1(), task2()]);

これらの注意点を意識すると、非同期処理のバグやパフォーマンス問題を未然に防ぐことができます。開発では「意図的に待つ」「例外に備える」「順序を把握する」を心がけましょう。

まとめ

  • 非同期処理は、重たい処理の間もアプリ全体を止めないために必要
  • Promiseで非同期処理を扱うと可読性が向上
  • async/awaitはPromiseをさらに読みやすくした書き方

今後は、非同期処理が不可欠な場面(API通信、ファイル操作など)で、async/awaitを中心に使っていくのが主流です。

参考リンク