コンテンツにスキップ

8-4. 演習問題


1-1. 次のコードの出力順序として正しいものはどれか。

console.log("A");
setTimeout(() => console.log("B"), 0);
console.log("C");
  • A. ABC
  • B. ACB
  • C. BAC
  • D. CAB
解答と解説

正解:B

setTimeout のタイマーが 0 ミリ秒でも、コールバックはタスクキューに追加され、現在のコールスタックが空になった後に実行される。

1. "A" を出力(同期処理)
2. setTimeout を登録(0ms後にコールバックをキューに積む)
3. "C" を出力(同期処理)
4. コールスタックが空になる
5. タスクキューから "B" のコールバックを取り出して実行

これがイベントループの仕組みだ。タイムアウト 0 は「今のコードが終わった直後」を意味し、「今すぐ」ではない。


1-2. Promiseの状態として正しい組み合わせはどれか。

  • A. waitingsuccessfailure
  • B. idleresolvedrejected
  • C. pendingfulfilledrejected
  • D. loadingdoneerror
解答と解説

正解:C

Promiseは3つの状態を持つ:

  • pending:初期状態。まだ解決も拒否もされていない
  • fulfilled:処理が成功し、resolve() が呼ばれた状態
  • rejected:処理が失敗し、reject() が呼ばれた状態

fulfilledrejected を合わせて settled(確定済み)と呼ぶこともある。一度 fulfilledrejected になった Promise は変化しない。


1-3. 次のコードの問題点はどれか。

async function loadData() {
const response = fetch("/api/data");
const data = response.json();
return data;
}
  • A. async 関数は fetch を呼べない
  • B. await がないため responsedata は 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. 次のコードの( )に入るコードを答えよ。

// (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;
}
}
解答欄
解答と解説
  1. (resolve, reject) 1b. resolve 2a. then 2b. catch 3a. try 3b. 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-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 でエラー処理も一カ所にまとめられるのが利点だ。

なお、fetchUserPostsuser.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 でタイムアウトを設定する

TODOを埋めて、イベントループの出力順と直列・並列の速度差を確認せよ。

  1. 課題1のコメントに出力される順番を予想してから実行する
  2. delay 関数の TODO を実装する
  3. 課題2・3の TODO を埋めて直列と並列の時間差を確かめる
実行プレイグラウンド
編集内容は自動保存されます / Ctrl+Enter でも実行できます
出力
「実行」を押すと、ここに結果が表示されます。