同期処理と非同期処理の違いをわかりやすく解説
プログラミングでは「同期処理」と「非同期処理」という2つの処理方式があります。特にWeb開発ではAPIの呼び出しやファイルの読み書きなど、非同期処理を使う場面が多く出てきます。この記事では、その違いを具体的な例でわかりやすく解説します。
同期処理と非同期処理の違い
同期処理
同期処理は、処理を上から順番に1つずつ実行する方式です。ある処理が終わるまで次の処理には進みません。
同期処理のイメージ(レストランのカウンター席):
1. 注文する → 待つ...
2. 料理が届く → 食べる
3. お会計する → 完了
すべて順番に実行される
// 同期処理の例
console.log("処理1: 開始");
console.log("処理2: 計算中...");
const result = 1 + 2;
console.log("処理3: 結果は " + result);
// 出力(順番通り):
// 処理1: 開始
// 処理2: 計算中...
// 処理3: 結果は 3
非同期処理
非同期処理は、時間のかかる処理の完了を待たずに次の処理に進む方式です。結果が返ってきたら、あらためてその結果を処理します。
非同期処理のイメージ(フードコート):
1. A店で注文する → 番号札をもらう
2. B店で注文する → 番号札をもらう
3. 席で待つ
4. A店の料理ができた → 受け取る
5. B店の料理ができた → 受け取る
待ち時間を有効活用できる
// 非同期処理の例
console.log("処理1: 開始");
setTimeout(() => {
console.log("処理2: 2秒後に実行");
}, 2000);
console.log("処理3: すぐに実行");
// 出力:
// 処理1: 開始
// 処理3: すぐに実行 ← 処理2を待たずに実行される
// 処理2: 2秒後に実行 ← 2秒後に実行される
setTimeoutは指定した時間後に処理を実行する関数です。完了を待たずに次のconsole.logが先に実行されている点に注目してください。
なぜ非同期処理が必要なのか
Web開発では以下のような「時間のかかる処理」が多く発生します。
- サーバーへのAPIリクエスト(ネットワーク通信)
- ファイルの読み書き
- データベースへのクエリ
- タイマー処理
これらを同期的に処理すると、通信の応答を待っている間にプログラム全体が止まってしまいます。
// もし fetch が同期処理だったら(実際にはこうはならない)
// const data = fetch("https://api.example.com/data"); // 3秒かかる
// この3秒間、ページが完全に固まる
// ボタンもクリックできない、スクロールもできない
非同期処理を使えば、通信の応答を待っている間も他の処理(ユーザーの操作への応答など)を続けることができます。
JavaScriptの非同期処理
コールバック関数
最も基本的な非同期処理のパターンです。
// コールバック関数を使う例
function fetchData(callback) {
setTimeout(() => {
const data = { name: "太郎", age: 25 };
callback(data);
}, 1000);
}
fetchData((data) => {
console.log(data.name); // 1秒後に "太郎"
});
コールバックが深くなると「コールバック地獄」と呼ばれる読みにくいコードになります。
// コールバック地獄の例(読みにくい)
getUser(userId, (user) => {
getPosts(user.id, (posts) => {
getComments(posts[0].id, (comments) => {
console.log(comments);
});
});
});
Promise
Promiseはコールバック地獄を解決するために導入された仕組みです。
// Promiseを使う例
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve({ name: "太郎", age: 25 });
} else {
reject(new Error("データの取得に失敗"));
}
}, 1000);
});
}
// .then() でチェーンできる
fetchData()
.then((data) => {
console.log(data.name); // "太郎"
return data.age;
})
.then((age) => {
console.log(`年齢: ${age}`); // "年齢: 25"
})
.catch((error) => {
console.error(error.message);
});
Promiseは3つの状態を持ちます。
- pending: 処理中(まだ結果が出ていない)
- fulfilled: 成功(resolveが呼ばれた)
- rejected: 失敗(rejectが呼ばれた)
async / await
async/awaitはPromiseをさらに読みやすく書くための構文です。現在の主流の書き方です。
// async/awaitを使う例
async function main() {
try {
const response = await fetch("https://api.example.com/users");
const users = await response.json();
console.log(users);
} catch (error) {
console.error("エラー:", error.message);
}
}
main();
awaitは「この処理が完了するまで待つ」という意味です。見た目は同期処理のように上から順に読めますが、実際には非同期処理が行われています。
// 複数の非同期処理を並行実行
async function fetchAllData() {
// Promise.allで並行実行(順番に待つより速い)
const [users, posts] = await Promise.all([
fetch("/api/users").then((r) => r.json()),
fetch("/api/posts").then((r) => r.json()),
]);
console.log("ユーザー数:", users.length);
console.log("投稿数:", posts.length);
}
Promise.allを使うと、複数の非同期処理を同時に開始し、すべてが完了するのを待つことができます。
Pythonの非同期処理
Pythonでもasync/awaitを使った非同期処理が可能です。
import asyncio
async def fetch_data(name, delay):
print(f"{name}: 開始")
await asyncio.sleep(delay) # 非同期の待機
print(f"{name}: 完了({delay}秒後)")
return f"{name}の結果"
async def main():
# 並行実行
results = await asyncio.gather(
fetch_data("タスクA", 2),
fetch_data("タスクB", 1),
fetch_data("タスクC", 3),
)
print(results)
asyncio.run(main())
# 出力:
# タスクA: 開始
# タスクB: 開始
# タスクC: 開始
# タスクB: 完了(1秒後)
# タスクA: 完了(2秒後)
# タスクC: 完了(3秒後)
# ['タスクAの結果', 'タスクBの結果', 'タスクCの結果']
3つのタスクが並行して実行されるため、合計6秒ではなく3秒(最長のタスクの時間)で完了します。
よくある間違いと対策
間違い1: awaitを忘れる
// NG: awaitなしだとPromiseオブジェクトが返る
async function getUser() {
const response = fetch("/api/user"); // awaitがない
console.log(response); // Promise { <pending> }
}
// OK: awaitをつける
async function getUser() {
const response = await fetch("/api/user");
const data = await response.json();
console.log(data); // 実際のデータ
}
間違い2: async関数の外でawaitを使う
// NG: トップレベルでawait(モジュールでなければエラー)
// const data = await fetch("/api/data");
// OK: async関数の中で使う
async function init() {
const data = await fetch("/api/data");
}
init();
間違い3: エラーハンドリングを忘れる
// NG: エラーが起きても何も起きない(サイレント失敗)
async function fetchData() {
const response = await fetch("/api/data");
const data = await response.json();
return data;
}
// OK: try-catchでエラーを捕捉
async function fetchData() {
try {
const response = await fetch("/api/data");
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error("データ取得エラー:", error.message);
return null;
}
}
非同期処理は最初は理解しにくいですが、Web開発では避けて通れない技術です。まずはasync/awaitの基本パターンを覚え、APIの呼び出しやタイマー処理で実践してみてください。