8-4. 演習問題
問題1:選択問題
Section titled “問題1:選択問題”1-1. 次のコードの出力順序として正しいものはどれか。
console.log("A");setTimeout(() => console.log("B"), 0);console.log("C");- A.
A→B→C - B.
A→C→B - C.
B→A→C - D.
C→A→B
解答と解説
正解:B
setTimeout のタイマーが 0 ミリ秒でも、コールバックはタスクキューに追加され、現在のコールスタックが空になった後に実行される。
1. "A" を出力(同期処理)2. setTimeout を登録(0ms後にコールバックをキューに積む)3. "C" を出力(同期処理)4. コールスタックが空になる5. タスクキューから "B" のコールバックを取り出して実行これがイベントループの仕組みだ。タイムアウト 0 は「今のコードが終わった直後」を意味し、「今すぐ」ではない。
1-2. Promiseの状態として正しい組み合わせはどれか。
- A.
waiting・success・failure - B.
idle・resolved・rejected - C.
pending・fulfilled・rejected - D.
loading・done・error
解答と解説
正解:C
Promiseは3つの状態を持つ:
pending:初期状態。まだ解決も拒否もされていないfulfilled:処理が成功し、resolve()が呼ばれた状態rejected:処理が失敗し、reject()が呼ばれた状態
fulfilled と rejected を合わせて settled(確定済み)と呼ぶこともある。一度 fulfilled や rejected になった Promise は変化しない。
1-3. 次のコードの問題点はどれか。
async function loadData() { const response = fetch("/api/data"); const data = response.json(); return data;}- A.
async関数はfetchを呼べない - B.
awaitがないためresponseとdataは Promise オブジェクトになる - C.
returnで Promise を返してはいけない - D. 問題はない
解答と解説
正解:B
await を忘れると fetch() は Promise オブジェクトを返したままになる。response は Response オブジェクトではなく Promise<Response> になるため、response.json() は存在しないメソッドへの呼び出しになってしまう。
正しいコード:
async function loadData() { const response = await fetch("/api/data"); // await が必要 const data = await response.json(); // await が必要 return data;}async 関数の中で非同期処理を使うときは、基本的に await を忘れないように注意する。
1-4. 次のコードの実行にかかるおよその時間はどれか。
async function task1() { /* 1秒かかる処理 */ }async function task2() { /* 1秒かかる処理 */ }
async function run() { await task1(); await task2();}- A. 約0.5秒
- B. 約1秒
- C. 約2秒
- D. 即時
解答と解説
正解:C
await task1() で task1 の完了を待ってから await task2() を開始するため、順次実行になり合計で約2秒かかる。
並列実行にする場合は Promise.all を使う:
async function run() { await Promise.all([task1(), task2()]); // task1 と task2 を同時に開始し、両方完了したら先へ進む // 合計で約1秒(最も遅いものに合わせる)}独立した非同期処理は Promise.all でまとめて並列実行するのが効率的だ。
問題2:穴埋め問題
Section titled “問題2:穴埋め問題”2. 次のコードの( )に入るコードを答えよ。
// (1) 1秒後に "done" で resolve される Promise を作るconst p = new Promise((1)=> { setTimeout(() => (1b)("done"), 1000);});
// (2) Promise チェーン:getUserById → 成功時は user.name を出力、失敗時は "Not found" を出力getUserById(1) .(2a)(user => console.log(user.name)) .(2b)(err => console.log("Not found"));
// (3) async/await でエラーを安全に捕捉するasync function safeLoad() { (3a) { const data = await fetchData(); return data; } (3b) (error) { console.error(error); return null; }}解答と解説
(resolve, reject)1b.resolve2a.then2b.catch3a.try3b.catch
解説
(1) new Promise() のコンストラクタには (resolve, reject) の2つのコールバック引数を受け取る関数を渡す。成功時は resolve(値) を、失敗時は reject(エラー) を呼ぶ。
(2) .then(成功ハンドラ).catch(失敗ハンドラ) のパターンはPromiseチェーンの基本。.catch() は直前のどの .then() で発生したエラーも捕捉できる。
(3) async/await では同期処理と同様に try { ... } catch (error) { ... } でエラーを捕捉する。await 先の Promise が reject された場合、catch ブロックに飛ぶ。
問題3:記述問題
Section titled “問題3:記述問題”3-1. 次のコードを async/await を使って書き直せ。
function loadUserProfile(userId) { return fetchUser(userId) .then(user => { return fetchUserPosts(user.id) .then(posts => { return { user, posts }; }); }) .catch(error => { console.error("エラー:", error); return null; });}解答と解説
async function loadUserProfile(userId) { try { const user = await fetchUser(userId); const posts = await fetchUserPosts(user.id); return { user, posts }; } catch (error) { console.error("エラー:", error); return null; }}async/await を使うと、Promiseチェーンのネストが解消されて処理の流れが上から下へ読めるようになる。try/catch でエラー処理も一カ所にまとめられるのが利点だ。
なお、fetchUserPosts が user.id に依存している(fetchUser の結果を待たないといけない)ため、この場合は Promise.all で並列化できないことに注意する。
3-2. 次の要件を満たす fetchWithRetry 関数を実装せよ。
fetch(url)を呼び出す- 失敗した場合、最大
maxRetry回まで再試行する - 全て失敗した場合はエラーをスローする
解答と解説
async function fetchWithRetry(url, maxRetry = 3) { let lastError; for (let attempt = 1; attempt <= maxRetry; attempt++) { try { const response = await fetch(url); if (!response.ok) throw new Error(`HTTP ${response.status}`); return await response.json(); } catch (error) { lastError = error; console.warn(`試行 ${attempt}/${maxRetry} 失敗: ${error.message}`); } } throw lastError; // 全試行失敗}
// 使い方try { const data = await fetchWithRetry("/api/data", 3); console.log(data);} catch (error) { console.error("全ての試行が失敗しました:", error.message);}実務での改善点:
- 再試行の間隔を設ける(指数バックオフ):
await new Promise(r => setTimeout(r, 1000 * attempt)) - ネットワークエラーと4xxエラーを区別する(4xxは再試行しない)
- AbortController でタイムアウトを設定する
問題4:ハンズオン
Section titled “問題4:ハンズオン”TODOを埋めて、イベントループの出力順と直列・並列の速度差を確認せよ。
取り組む内容
Section titled “取り組む内容”- 課題1のコメントに出力される順番を予想してから実行する
delay関数の TODO を実装する- 課題2・3の TODO を埋めて直列と並列の時間差を確かめる