同期と非同期 — コールバック、Promise、async/await
このトピックを終えると
同期と非同期の違いを説明でき、コールバック → Promise → async/await の発展の過程を理解します。
同期 — 一行ずつ順番に
同期 (Synchronous) は、コードが上から下に向かって一行ずつ実行される方法です。前の処理が終わらなければ、次の処理は開始されません。
console.log("1. 注文受付");
console.log("2. コーヒーの製造 (3分かかる)");
console.log("3. コーヒーの提供");
// 1 → 2 → 3 の順に実行直感的ですが、問題点があります。コーヒーを製造している間は、何もできません。 ファイルの読み込みに5秒かかる場合、その間サーバーが停止し、データベースの照会に2秒かかる場合、その間他のユーザーの要求を処理できません。
非同期 — 待たずに次の処理へ
非同期 (Asynchronous) は、時間がかかる処理を開始し、完了を待たずに次のコードを先に実行します。処理が完了したら、後で結果を受け取ります。
const fs = require("fs");
console.log("1. ファイルの読み込み開始");
// 非同期 — ファイルの読み込みを開始し、すぐに次の処理へ
fs.readFile("data.txt", "utf8", (err, data) => {
console.log("3. ファイルの読み込み完了:", data);
});
console.log("2. 次の処理を実行");
// 出力順序:
// 1. ファイルの読み込み開始
// 2. 次の処理を実行 ← 待たずに先に実行!
// 3. ファイルの読み込み完了: (ファイルの内容)出力順序は 1 → 2 → 3 ではなく、1 → 2 → 3 です。数字だけ見ると同じに見えますが、2 番目が 3 番目よりも先に実行されます。ファイルが読み込まれるのを待たなかったためです。
コールバック — 非同期の最初の方法
上記のコードで (err, data) => { ... } の部分が コールバック (callback) 関数です。「処理が完了したら、この関数を呼び出してね」という意味です。
問題は、非同期処理が連続して必要な場合に発生します。
// コールバック地獄 (callback hell)
fs.readFile("a.txt", "utf8", (err, a) => {
fs.readFile("b.txt", "utf8", (err, b) => {
fs.readFile("c.txt", "utf8", (err, c) => {
console.log(a + b + c);
// インデントがどんどん深くなる...
});
});
});このようにコールバックの中にコールバックがネストすると、コードが読みづらくなります。これを コールバック地獄 と呼びます。
Promise — コールバック地獄の解決
Promise は、「後で結果を返すという約束」です。.then() で連結すると、コールバック地獄なしに順番に処理できます。
const fs = require("fs").promises;
fs.readFile("a.txt", "utf8")
.then(a => {
console.log("a の読み込み完了");
return fs.readFile("b.txt", "utf8");
})
.then(b => {
console.log("b の読み込み完了");
return fs.readFile("c.txt", "utf8");
})
.then(c => {
console.log("c の読み込み完了");
})
.catch(err => {
console.error("エラー発生:", err.message);
});インデントが深くなりません。.catch() でエラー処理も一箇所でまとめて行います。
async/await — 非同期を同期のように
async/await は、Promise をより読みやすく書くための構文です。非同期コードが同期コードのように上から下に向かって読みやすくなります。
const fs = require("fs").promises;
async function readAll() {
try {
const a = await fs.readFile("a.txt", "utf8");
const b = await fs.readFile("b.txt", "utf8");
const c = await fs.readFile("c.txt", "utf8");
console.log(a + b + c);
} catch (err) {
console.error("エラー:", err.message);
}
}
readAll();await は「この処理が完了するまで待つ」という意味ですが、プログラム全体を停止させるのではなく、この関数だけを一時的に停止させ、他の処理は継続します。await を使用するには、関数に async を付ける必要があります。
現在の JavaScript では、ほとんどの場合 async/await を使用します。AI に非同期コードを要求する場合も、「async/await で記述してください」と言うと、最も読みやすいコードが生成されます。
→ 開発に適用: DevBench — AjaxとFetch