同期処理と非同期処理
同期処理はコードを順次実行し、結果を待ってから次の処理に進みます。非同期処理は実行後に結果を待たず、後続のコードを継続実行し、将来完了時にコールバック関数で結果を返します。
const total = 5 + 3;
console.log(total); // 8
setTimeout(() => {
console.log('非同期処理完了');
}, 1000);
document.querySelector('#submit').addEventListener('click', () => {
console.log('ボタンクリック');
});
document.body.style.backgroundColor = '#f0f0f0';
console.log('処理継続');
出力順序: 8 → 処理継続 → 非同期処理完了(1秒後)
コールバック地獄
コールバック関数内でのネストが深くなると、可読性低下やエラー処理困難などの問題が発生します。
fetchProvinceData().then(provinceResult => {
const province = provinceResult.regions[0];
updateElement('.province', province);
fetchCityData(province).then(cityResult => {
const city = cityResult.areas[0];
updateElement('.city', city);
fetchDistrictData(province, city).then(districtResult => {
updateElement('.district', districtResult.locations[0]);
});
});
});
Promiseの連鎖処理
then()メソッドが新たなPromiseを返す特性を利用し、非同期処理を直列的に連結します。
const initialPromise = new Promise((resolve) => {
setTimeout(() => resolve('東京都'), 1000);
});
initialPromise
.then(province => {
console.log(province);
return new Promise(resolve => {
setTimeout(() => resolve(`${province} - 新宿区`), 1000);
});
})
.then(area => {
console.log(area);
});
async/awaitの活用
async関数内でawaitを使用すると、Promiseの解決を待機し、同期処理のような記述が可能になります。
async function fetchRegionData() {
const provinceRes = await fetch('api/provinces');
const province = provinceRes.regions[0];
const cityRes = await fetch(`api/cities?province=${province}`);
const city = cityRes.areas[0];
const districtRes = await fetch(`api/districts?province=${province}&city=${city}`);
const district = districtRes.locations[0];
updateUI(province, city, district);
}
fetchRegionData();
エラー処理
try/catch構文で非同期処理中のエラーを捕捉します。
async function loadData() {
try {
const response = await fetch('api/data');
if (!response.ok) throw new Error('APIエラー');
return response.json();
} catch (error) {
console.error('データ取得失敗:', error);
return null;
}
}
イベントループの仕組み
JavaScriptの実行モデルでは、メインスレッドがタスクキューからコールバックを取り出し実行します。
console.log('開始');
setTimeout(() => console.log('タイマー1'), 0);
Promise.resolve().then(() => console.log('プロミス1'));
setTimeout(() => console.log('タイマー2'), 100);
console.log('終了');
出力順序: 開始 → 終了 → プロミス1 → タイマー1 → タイマー2
マクロタスクとマイクロタスク
非同期処理は実行タイミングにより分類されます。
- マクロタスク: setTimeout, setInterval, UIイベント
- マイクロタスク: Promise, queueMicrotask
console.log('スクリプト開始');
setTimeout(() => console.log('マクロタスク'), 0);
Promise.resolve().then(() => console.log('マイクロタスク'));
console.log('スクリプト終了');
出力順序: スクリプト開始 → スクリプト終了 → マイクロタスク → マクロタスク
Promise.allによる並列処理
複数のPromiseを同時実行し、全て成功した場合に結果をまとめて取得します。
const fetchUser = fetch('api/users');
const fetchProducts = fetch('api/products');
const fetchOrders = fetch('api/orders');
Promise.all([fetchUser, fetchProducts, fetchOrders])
.then(responses => {
const [userRes, productsRes, ordersRes] = responses;
console.log('全データ取得完了');
})
.catch(error => {
console.error('いずれかのリクエストが失敗:', error);
});