「Ajax通信のコールバック、入れ子が深くなってきて辛い…」 「Promiseは分かるけど、async/awaitってどう使えばいいの?」 「$.ajaxの代わりにfetchを使うって聞いたけど、使い方が分からない」
前回はDOM操作のモダンな書き方を学びました。今回は、非同期処理の書き方の進化について見ていきましょう。jQueryの$.ajax
から、最新のasync/await
まで、段階的に理解していきます。
コールバック地獄を体験する
まず、従来のjQueryでの非同期処理を見てみましょう:
JS
// jQuery: ユーザー情報を取得して、その友達リストを取得し、最新の投稿を表示する
$.ajax({
url: '/api/user/1',
success: function(user) {
console.log('ユーザー取得完了');
$.ajax({
url: '/api/friends/' + user.id,
success: function(friends) {
console.log('友達リスト取得完了');
$.ajax({
url: '/api/posts/' + friends[0].id,
success: function(posts) {
console.log('投稿取得完了');
$('#result').text(posts[0].title);
},
error: function(error) {
console.error('投稿取得エラー:', error);
}
});
},
error: function(error) {
console.error('友達リスト取得エラー:', error);
}
});
},
error: function(error) {
console.error('ユーザー取得エラー:', error);
}
});
このコードの問題点:
- ネストが深くなり、可読性が低下
- エラーハンドリングが各レベルで必要
- 並列処理や条件分岐が複雑になりやすい
Promiseによる改善
JS
// fetch APIを使用した最初のアプローチ
fetch('/api/user/1')
.then(response => response.json())
.then(user => {
console.log('ユーザー取得完了');
return fetch('/api/friends/' + user.id);
})
.then(response => response.json())
.then(friends => {
console.log('友達リスト取得完了');
return fetch('/api/posts/' + friends[0].id);
})
.then(response => response.json())
.then(posts => {
console.log('投稿取得完了');
document.querySelector('#result').textContent = posts[0].title;
})
.catch(error => {
console.error('エラーが発生しました:', error);
});
Promiseの特徴:
- チェーンによる直線的な記述が可能
- エラーハンドリングの一元化
- 非同期処理の合成が容易
async/awaitによる更なる改善
JS
// async/awaitを使用した最新のアプローチ
async function fetchUserData() {
try {
const userResponse = await fetch('/api/user/1');
const user = await userResponse.json();
console.log('ユーザー取得完了');
const friendsResponse = await fetch('/api/friends/' + user.id);
const friends = await friendsResponse.json();
console.log('友達リスト取得完了');
const postsResponse = await fetch('/api/posts/' + friends[0].id);
const posts = await postsResponse.json();
console.log('投稿取得完了');
document.querySelector('#result').textContent = posts[0].title;
} catch (error) {
console.error('エラーが発生しました:', error);
}
}
// 関数の実行
fetchUserData();
async/awaitのメリット:
- 同期処理のように読みやすい
- try/catchによる直感的なエラーハンドリング
- デバッグがしやすい
実践的なパターン
並列リクエストの処理
JS
// jQuery
$.when(
$.ajax('/api/users'),
$.ajax('/api/posts'),
$.ajax('/api/comments')
).then(function(users, posts, comments) {
console.log(users[0], posts[0], comments[0]);
});
// Modern JavaScript
async function fetchAllData() {
try {
const [users, posts, comments] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]);
console.log(users, posts, comments);
} catch (error) {
console.error('エラー:', error);
}
}
ポイント:
Promise.all
で複数のリクエストを並列実行- 分割代入で結果を簡潔に取得
- エラーは一括でキャッチ可能
リクエストのタイムアウト設定
JS
// jQuery
$.ajax({
url: '/api/data',
timeout: 5000,
success: function(data) {
console.log(data);
},
error: function(xhr, status, error) {
if (status === 'timeout') {
console.log('タイムアウトしました');
}
}
});
// Modern JavaScript
async function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
signal: controller.signal
});
clearTimeout(timeoutId);
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
throw new Error('タイムアウトしました');
}
throw error;
}
}
ポイント:
AbortController
でリクエストのキャンセルが可能- タイムアウト時間を柔軟に設定可能
- エラーの種類を区別して処理可能
リトライ処理の実装
JS
// Modern JavaScript
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url);
return await response.json();
} catch (error) {
if (i === retries - 1) throw error;
console.log(`リトライ ${i + 1}/${retries}`);
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
}
リトライ処理のポイント:
- エラー発生時に一定回数リトライ
- 指数バックオフで待機時間を増加
- 最終リトライ後はエラーをスロー
よくある非同期処理のパターン
データの検証付きフェッチ
JS
// Modern JavaScript
async function fetchValidatedData(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (!data.success) {
throw new Error(data.error || 'データの検証に失敗しました');
}
return data.result;
}
ポイント:
- HTTPステータスのチェック
- レスポンスデータの検証
- エラーメッセージの明確化
まとめ
非同期処理は、モダンJavaScriptで大きく進化しました:
- コールバック → Promise → async/await
- より直感的な記述が可能に
- エラーハンドリングが簡潔に
- 高度な制御が容易に
次回は「モジュール化への第一歩 – import/export を理解する」について解説し、コードの構造化について学んでいきましょう。