メインコンテンツまでスキップ

非同期処理

時間のかかる処理(通信・タイマーなど)を、他の処理を止めずに進めるための仕組みです。

非同期処理とは

非同期処理は「処理を始めたら結果を待たずに次へ進み、完了したら通知を受け取る」流れで動きます。

コールバック関数

処理の完了後に呼んでもらう関数を渡す、最も基本的な方法です。

function fetchData(callback) {
setTimeout(() => {
const data = { name: "データ", value: 42 };
callback(data);
}, 1000);
}

fetchData((data) => {
console.log("データを取得しました:", data);
});
console.log("fetchData を呼び出しました"); // この行が先に実行される
コールバック地獄

コールバックを何段も入れ子にすると、右にどんどん深くなる読みづらいコード(コールバック地獄)になります。これを解決するために Promise や async/await が導入されました。

step1((result1) => {
step2(result1, (result2) => {
step3(result2, (result3) => {
console.log("最終結果:", result3); // 深くなって読みづらい
});
});
});

Promise

Promise は「いずれ結果が返る約束」を表すオブジェクトです。次の状態を遷移します。

const myPromise = new Promise((resolve, reject) => {
const success = true;
if (success) {
resolve("処理が成功しました!");
} else {
reject(new Error("エラーが発生しました"));
}
});

myPromise
.then((result) => console.log(result)) // 成功時
.catch((error) => console.error(error)) // 失敗時
.finally(() => console.log("処理完了")); // 成功・失敗どちらでも

複数の Promise をまとめて扱う

const p1 = Promise.resolve("成功1");
const p2 = Promise.reject("失敗");
const p3 = Promise.resolve("成功2");

// Promise.all: すべて成功で配列を返す。1 つでも失敗すると全体が失敗
Promise.all([p1, p3]).then((results) => {
console.log(results); // ['成功1', '成功2']
});

// Promise.allSettled: 全部待つ。失敗があっても止まらない (ES2020)
Promise.allSettled([p1, p2, p3]).then((results) => {
console.log(results);
// [{ status: 'fulfilled', value: '成功1' }, { status: 'rejected', reason: '失敗' }, ...]
});

// Promise.any: 最初に成功した 1 つを返す (ES2021)
Promise.any([p2, p1]).then((result) => {
console.log(result); // 成功1
});
使い分け
  • すべて成功してほしい → Promise.all
  • 成否を問わず全部の結果が欲しい → Promise.allSettled
  • どれか 1 つ成功すればいい → Promise.any

Async/Await(ES2017)

async / await を使うと、非同期処理を同期処理のように直線的に書けます。内部的には Promise を使っています。

async function fetchUserData() {
try {
// await で Promise の結果を待つ
const response = await fetch("https://api.example.com/user");
const userData = await response.json();
console.log("ユーザーデータ:", userData);
return userData;
} catch (error) {
console.error("データの取得に失敗しました:", error);
throw error;
}
}

直列と並列

// 直列: 前の await が終わってから次へ (合計時間が長い)
const a = await fetchA();
const b = await fetchB();

// 並列: 同時に走らせて両方を待つ (速い)
const [x, y] = await Promise.all([fetchA(), fetchB()]);
実行順序とマイクロタスク

Promise.then() で登録した処理は マイクロタスク というキューに入り、setTimeout などの通常タスクより先に実行されます。「同期コード → Promise の then → setTimeout」の順で実行されると覚えておくと、非同期の実行順序を予測しやすくなります。

次に読む