Skip to content

10. 非同期処理(コールバック、プロミス、async/await)

JavaScript はシングルスレッドで動作しますが、非同期処理を利用することで、時間のかかる操作を効率的に扱うことができます。この章では、JavaScript における非同期処理の基本として、コールバック、プロミス、そして最新の async/await を紹介します。

10.1 コールバック

10.1.1 コールバックの基本

コールバック関数は、他の関数に引数として渡され、他の関数の実行が完了した後に呼び出される関数です。例えば、以下のように使います:

function fetchData(callback) {
  setTimeout(() => {
    // データを取得したと仮定します
    const data = { user: "John Doe" };
    callback(data);
  }, 1000); // 1秒後に実行
}

function handleData(data) {
  console.log("データを受け取りました:", data);
}

fetchData(handleData);

10.1.2 コールバック地獄

複雑な非同期操作をコールバックで連続して処理することは、可読性が低く、エラーハンドリングも難しくなります。これは「コールバック地獄」と呼ばれます。

function firstTask(callback) {
  setTimeout(() => {
    console.log("First task completed");
    callback();
  }, 1000);
}

function secondTask(callback) {
  setTimeout(() => {
    console.log("Second task completed");
    callback();
  }, 1000);
}

function thirdTask(callback) {
  setTimeout(() => {
    console.log("Third task completed");
    callback();
  }, 1000);
}

firstTask(() => {
  secondTask(() => {
    thirdTask(() => {
      console.log("All tasks completed");
    });
  });
});

10.2 プロミス

10.2.1 プロミスの基本

プロミス(Promise)は、非同期操作の結果を扱うオブジェクトで、3 つの状態を持ちます:pending(保留中)、fulfilled(解決済み)、rejected(拒否済み)。以下はプロミスを使った例です:

function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const success = true; // 成功したかどうかを仮定
      if (success) {
        resolve({ user: "John Doe" }); // 成功した場合のデータ
      } else {
        reject("データ取得に失敗しました"); // エラーメッセージ
      }
    }, 1000);
  });
}

fetchData()
  .then((data) => {
    console.log("データを受け取りました:", data);
  })
  .catch((error) => {
    console.error("エラーが発生しました:", error);
  });

10.2.2 チェーンによる連続処理

プロミスはチェーン可能で、連続的な非同期処理を簡潔に書くことができます。

function firstTask() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log("First task completed");
      resolve();
    }, 1000);
  });
}

function secondTask() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log("Second task completed");
      resolve();
    }, 1000);
  });
}

function thirdTask() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log("Third task completed");
      resolve();
    }, 1000);
  });
}

firstTask()
  .then(secondTask)
  .then(thirdTask)
  .then(() => {
    console.log("All tasks completed");
  });

10.3 async/await

10.3.1 async/await の基本

asyncawait は、プロミスをさらに簡潔に記述できるようにする構文です。async 関数は常にプロミスを返し、await 式はプロミスの結果が解決されるまで待ちます。

async function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({ user: "John Doe" });
    }, 1000);
  });
}

async function getData() {
  try {
    const data = await fetchData();
    console.log("データを受け取りました:", data);
  } catch (error) {
    console.error("エラーが発生しました:", error);
  }
}

getData();

10.3.2 複数の非同期処理の直列実行

async/await を使うことで、複数の非同期操作をシンプルに直列実行できます。

async function firstTask() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log("First task completed");
      resolve();
    }, 1000);
  });
}

async function secondTask() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log("Second task completed");
      resolve();
    }, 1000);
  });
}

async function thirdTask() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log("Third task completed");
      resolve();
    }, 1000);
  });
}

async function runTasks() {
  await firstTask();
  await secondTask();
  await thirdTask();
  console.log("All tasks completed");
}

runTasks();

10.4 総括

この章では、JavaScript の非同期処理の基礎として、コールバック、プロミス、そして async/await を紹介しました。これらの技術を使い分けることで、効率的かつ可読性の高いコードを書くことができます。実際にコードを書いて試してみてください。