Day 9: 非同期処理
今日学ぶこと
- 同期処理と非同期処理の違い
- コールバック関数
- Promise
- async/await
- fetch API でデータ取得
同期処理と非同期処理
flowchart LR
subgraph Sync["同期処理"]
S1["タスク1"] --> S2["タスク2"] --> S3["タスク3"]
end
subgraph Async["非同期処理"]
A1["タスク1開始"]
A2["タスク2開始"]
A3["タスク3開始"]
A1 --> A2 --> A3
A1 -.->|"完了"| R1["結果1"]
A2 -.->|"完了"| R2["結果2"]
A3 -.->|"完了"| R3["結果3"]
end
style Sync fill:#3b82f6,color:#fff
style Async fill:#22c55e,color:#fff
同期処理(Synchronous)
コードが上から順番に、1つずつ実行されます。
console.log("1番目");
console.log("2番目");
console.log("3番目");
// 出力: "1番目" → "2番目" → "3番目"
非同期処理(Asynchronous)
時間のかかる処理を待たずに、次の処理に進みます。
console.log("1番目");
setTimeout(() => {
console.log("2番目(1秒後)");
}, 1000);
console.log("3番目");
// 出力: "1番目" → "3番目" → "2番目(1秒後)"
なぜ非同期処理が必要か?
| 処理の種類 | 例 | 理由 |
|---|---|---|
| ネットワーク通信 | API呼び出し | サーバーからの応答を待つ |
| ファイル操作 | ファイル読み込み | ディスクアクセスに時間がかかる |
| タイマー | setTimeout | 指定時間後に実行 |
| ユーザー入力 | クリック、キー入力 | いつ発生するか分からない |
コールバック関数
非同期処理の完了後に実行する関数を渡すパターンです。
function fetchData(callback) {
setTimeout(() => {
const data = { name: "太郎", age: 25 };
callback(data); // データ取得後にコールバックを実行
}, 1000);
}
fetchData((data) => {
console.log("データを受信:", data);
});
console.log("データ取得中...");
// 出力:
// "データ取得中..."
// "データを受信:" { name: "太郎", age: 25 }
コールバック地獄(Callback Hell)
コールバックをネストすると、コードが読みにくくなります。
// コールバック地獄の例
getUser(userId, (user) => {
getOrders(user.id, (orders) => {
getOrderDetails(orders[0].id, (details) => {
getProduct(details.productId, (product) => {
console.log(product);
// さらにネストが続く...
});
});
});
});
この問題を解決するために、Promise が導入されました。
Promise
Promise は「将来の値」を表すオブジェクトです。非同期処理の結果を扱う標準的な方法です。
flowchart LR
P["Promise<br/>作成"] --> Pending["Pending<br/>(待機中)"]
Pending -->|"成功"| Fulfilled["Fulfilled<br/>(成功)"]
Pending -->|"失敗"| Rejected["Rejected<br/>(失敗)"]
Fulfilled --> Then[".then()"]
Rejected --> Catch[".catch()"]
style P fill:#3b82f6,color:#fff
style Pending fill:#f59e0b,color:#000
style Fulfilled fill:#22c55e,color:#fff
style Rejected fill:#ef4444,color:#fff
Promise の3つの状態
| 状態 | 説明 |
|---|---|
| pending | 処理中(まだ結果が決まっていない) |
| fulfilled | 成功(値が確定した) |
| rejected | 失敗(エラーが発生した) |
Promise の作成
const promise = new Promise((resolve, reject) => {
// 非同期処理
setTimeout(() => {
const success = true;
if (success) {
resolve("成功しました!"); // 成功時
} else {
reject("エラーが発生しました"); // 失敗時
}
}, 1000);
});
Promise の使用(then/catch)
promise
.then((result) => {
console.log(result); // "成功しました!"
})
.catch((error) => {
console.log(error); // エラー時に実行
})
.finally(() => {
console.log("処理完了"); // 成功・失敗に関わらず実行
});
Promise チェーン
.then() を繋げて、順番に処理を実行できます。
function fetchUser(id) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id, name: "太郎" });
}, 500);
});
}
function fetchOrders(userId) {
return new Promise((resolve) => {
setTimeout(() => {
resolve([{ id: 1, item: "本" }, { id: 2, item: "ペン" }]);
}, 500);
});
}
// Promise チェーン
fetchUser(1)
.then((user) => {
console.log("ユーザー:", user);
return fetchOrders(user.id);
})
.then((orders) => {
console.log("注文:", orders);
})
.catch((error) => {
console.log("エラー:", error);
});
Promise の静的メソッド
// すべての Promise が成功したら
Promise.all([
fetch("/api/users"),
fetch("/api/products"),
fetch("/api/orders")
])
.then(([users, products, orders]) => {
console.log("全データ取得完了");
});
// 最初に完了した Promise の結果を取得
Promise.race([
fetch("/api/fast"),
fetch("/api/slow")
])
.then((result) => {
console.log("最速の結果:", result);
});
// すべての Promise が完了したら(成功・失敗問わず)
Promise.allSettled([
Promise.resolve("成功"),
Promise.reject("失敗")
])
.then((results) => {
console.log(results);
// [{ status: "fulfilled", value: "成功" },
// { status: "rejected", reason: "失敗" }]
});
| メソッド | 説明 |
|---|---|
Promise.all() |
すべて成功で完了、1つでも失敗で即reject |
Promise.race() |
最初に完了したものの結果 |
Promise.allSettled() |
すべて完了を待つ(成功・失敗問わず) |
Promise.any() |
最初に成功したものの結果 |
async/await
async/await は Promise をより読みやすく書くための構文です(ES2017+)。
flowchart TB
subgraph Traditional["従来のPromise"]
T1["promise.then()"] --> T2[".then()"] --> T3[".catch()"]
end
subgraph Modern["async/await"]
M1["async function"] --> M2["await promise"]
M2 --> M3["try/catch"]
end
style Traditional fill:#f59e0b,color:#000
style Modern fill:#22c55e,color:#fff
基本構文
// async 関数の宣言
async function fetchData() {
// await で Promise の完了を待つ
const result = await somePromise;
return result;
}
// アロー関数でも使える
const fetchData = async () => {
const result = await somePromise;
return result;
};
Promise vs async/await
// Promise を使った場合
function getData() {
return fetch("/api/data")
.then(response => response.json())
.then(data => {
console.log(data);
return data;
});
}
// async/await を使った場合
async function getData() {
const response = await fetch("/api/data");
const data = await response.json();
console.log(data);
return data;
}
エラーハンドリング
async function fetchUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const user = await response.json();
return user;
} catch (error) {
console.error("ユーザー取得エラー:", error);
throw error; // 必要に応じて再スロー
}
}
並列実行
// 順次実行(遅い)
async function sequential() {
const user = await fetchUser(1); // 1秒待つ
const orders = await fetchOrders(1); // さらに1秒待つ
// 合計: 2秒
}
// 並列実行(速い)
async function parallel() {
const [user, orders] = await Promise.all([
fetchUser(1), // 同時に開始
fetchOrders(1) // 同時に開始
]);
// 合計: 1秒(最も遅い処理の時間)
}
fetch API
fetch はネットワークリクエストを行うための標準APIです。
基本的な GET リクエスト
// シンプルなGETリクエスト
async function getUsers() {
const response = await fetch("https://api.example.com/users");
const users = await response.json();
return users;
}
// 使用例
getUsers().then(users => console.log(users));
POST リクエスト
async function createUser(userData) {
const response = await fetch("https://api.example.com/users", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(userData)
});
const newUser = await response.json();
return newUser;
}
// 使用例
createUser({ name: "太郎", email: "taro@example.com" })
.then(user => console.log("作成されたユーザー:", user));
レスポンスの処理
async function fetchData(url) {
const response = await fetch(url);
// ステータスコードの確認
console.log(response.status); // 200, 404, 500 など
console.log(response.ok); // true (200-299) or false
console.log(response.statusText); // "OK", "Not Found" など
// レスポンスヘッダー
console.log(response.headers.get("Content-Type"));
// ボディの取得(形式に応じて)
const json = await response.json(); // JSON
// const text = await response.text(); // テキスト
// const blob = await response.blob(); // バイナリ
return json;
}
エラーハンドリング
async function fetchWithErrorHandling(url) {
try {
const response = await fetch(url);
// HTTP エラーのチェック
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
if (error.name === "TypeError") {
// ネットワークエラー
console.error("ネットワークエラー:", error.message);
} else {
// その他のエラー
console.error("エラー:", error.message);
}
throw error;
}
}
実践例:APIからデータを取得して表示
// ユーザー一覧を取得して表示
async function displayUsers() {
const loadingEl = document.getElementById("loading");
const userListEl = document.getElementById("user-list");
const errorEl = document.getElementById("error");
try {
// ローディング表示
loadingEl.textContent = "読み込み中...";
errorEl.textContent = "";
// データ取得
const response = await fetch("https://jsonplaceholder.typicode.com/users");
if (!response.ok) {
throw new Error("ユーザー情報の取得に失敗しました");
}
const users = await response.json();
// 表示
userListEl.innerHTML = users
.map(user => `
<div class="user-card">
<h3>${user.name}</h3>
<p>Email: ${user.email}</p>
<p>City: ${user.address.city}</p>
</div>
`)
.join("");
} catch (error) {
errorEl.textContent = `エラー: ${error.message}`;
} finally {
loadingEl.textContent = "";
}
}
// ページ読み込み時に実行
displayUsers();
複数APIからデータを取得
async function fetchDashboardData() {
try {
// 並列でデータ取得
const [users, posts, comments] = await Promise.all([
fetch("https://api.example.com/users").then(r => r.json()),
fetch("https://api.example.com/posts").then(r => r.json()),
fetch("https://api.example.com/comments").then(r => r.json())
]);
return {
totalUsers: users.length,
totalPosts: posts.length,
totalComments: comments.length
};
} catch (error) {
console.error("ダッシュボードデータの取得に失敗:", error);
throw error;
}
}
まとめ
| 概念 | 説明 |
|---|---|
| 同期処理 | 順番に1つずつ実行 |
| 非同期処理 | 待たずに次へ進む |
| コールバック | 完了後に呼ばれる関数 |
| Promise | 将来の値を表すオブジェクト |
| async/await | Promiseを同期的に書く構文 |
| fetch | HTTPリクエスト用API |
重要ポイント
- 非同期処理はUI応答性を保つために必須
- Promiseで非同期処理を扱いやすく
- async/awaitで同期的な見た目に
- try/catchでエラーハンドリング
- Promise.allで並列実行を効率化
練習問題
問題1: Promise の作成
2秒後に「完了!」というメッセージを返すPromiseを作成してください。
問題2: async/await
以下のURLからデータを取得し、最初の5件のタイトルを表示する関数を作成してください。
https://jsonplaceholder.typicode.com/posts
問題3: エラーハンドリング
存在しないURLにアクセスした場合にエラーメッセージを表示する関数を作成してください。
問題4: 並列実行
以下の2つのURLから同時にデータを取得し、両方の結果を配列で返す関数を作成してください。
https://jsonplaceholder.typicode.com/users
https://jsonplaceholder.typicode.com/posts
チャレンジ問題
検索機能を実装してください:テキスト入力に応じてAPIからユーザーを検索し、300ms のデバウンス処理を追加してください。
参考リンク
次回予告: Day 10では「DOMとイベント」について学びます。Webページを動的に操作し、ユーザーインタラクションを実装する方法をマスターしましょう!