目的
try…catch の基本、例外の伝搬(同期)、非同期コールバックでの落とし穴、Promise / async–await での例外処理を確実に理解すること。
各問題は「問題 → 予想 → 実行例(出力) → 解説(ステップごと)」の順で示します。ブラウザの開発者コンソールか Node.js で実行して確認してください。
問題 1 — 同期:例外の伝搬(基本)
問題コード
function c() {
throw new Error('boom from c');
}
function b() {
c();
}
function a() {
b();
}
try {
a();
console.log('after a()');
} catch (e) {
console.log('caught:', e.message);
}
console.log('end');
JavaScriptまず予想してみよう:どこで例外が捕まる?after a() は表示される?
実行結果(期待)
caught: boom from c
end
解説(ステップ)
tryの中でa()を呼ぶ →a()がb()を呼ぶ →b()がc()を呼ぶ。c()がthrowする → 例外は呼び出しスタックをさかのぼって(伝搬して)いく。- 途中に
try…catchがあるので、そこで捕まりcatchブロックが実行される。 after a()はa()の実行が完全に成功した場合にのみ実行されるため、表示されない。- その後
console.log('end')が実行される(プログラムは停止しない)。
問題 2 — 非同期コールバック:外側の try は効くか?
問題コード
try {
setTimeout(() => {
throw new Error('later boom');
}, 100);
} catch (e) {
console.log('caught outer:', e.message);
}
console.log('after try');
JavaScriptまず予想してみよう:caught outer: が出るか?after try は出るか?
実行結果(想定)
after try
// 1秒未満で以下のようなブラウザのエラーメッセージ(外側 catch では捕まらない):
// Uncaught Error: later boom
解説(ステップ)
setTimeoutの呼び出し自体は同期的にすぐ終わる(コールバックは後で実行)。tryブロックはsetTimeoutの呼び出しを囲んでいるが、コールバック関数が実際に実行されるのはtryが既に終わった後。- よって、コールバック内の
throwは外側のcatchでは捕まらない。 - 対処法:コールバックの中で
try…catchを使う、または Promise 化して.catch()する。
問題 3 — 対処:コールバック内で try…catch
問題コード
setTimeout(() => {
try {
throw new Error('handled inside callback');
} catch (e) {
console.log('caught inside callback:', e.message);
}
}, 50);
console.log('after scheduling');
JavaScript予想:caught inside callback: が出る?after scheduling は早く出る?
実行結果(想定)
after scheduling
caught inside callback: handled inside callback
解説
- コールバックの中に
try…catchを置くことで、非同期に発生する例外をその場で処理できる。 - 外側の
tryに頼るより確実。
問題 4 — Promise 基本:reject を .catch() で受ける
問題コード
function asyncTask(shouldFail) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldFail) reject(new Error('promise failed'));
else resolve('ok');
}, 50);
});
}
asyncTask(true)
.then(result => console.log('result:', result))
.catch(err => console.log('promise caught:', err.message));
JavaScript予想:promise caught: が出る?
実行結果(想定)
promise caught: promise failed
解説
Promiseのrejectは.catch()で受けられる。- Promise ベースなら非同期エラーを外側で一箇所に集めやすい。
問題 5 — async/await と try/catch
問題コード
async function run() {
try {
const result = await asyncTask(true); // asyncTask は上と同じ
console.log('result:', result);
} catch (e) {
console.log('async/await caught:', e.message);
}
}
run();
JavaScript予想:どの catch が動く?
実行結果(想定)
async/await caught: promise failed
解説
awaitすると、Promise がrejectした場合はthrowが発生したように扱われる。try…catchで囲むと通常の例外と同じように捕まえられるため、同期風に書けて分かりやすい。
問題 6 — 混合:同期と非同期のエラー処理の違いを理解する
問題コード
function syncThrow() {
throw new Error('sync error');
}
function scheduleAsyncThrow() {
setTimeout(() => { throw new Error('async error'); }, 10);
}
try {
syncThrow();
scheduleAsyncThrow();
console.log('after calls');
} catch (e) {
console.log('caught outer:', e.message);
}
JavaScript予想:どのエラーが caught outer に捕まる?after calls は出る?
実行結果(想定)
caught outer: sync error
// 'async error' はブラウザ/Nodeの未捕捉エラーとして表示される(outerでは捕まらない)
解説
syncThrow()が即座にthrowするため、tryの中で捕まる。scheduleAsyncThrow()のthrowは後で実行されるため外側のtryでは捕まらない。after callsはsyncThrow()のthrowによって実行されない(tryを抜けて catch に入るため)。
問題 7 — 実践:非同期処理を Promise 化してまとめてエラーハンドリング
問題コード
function task1() {
return new Promise((res) => setTimeout(() => res('t1 ok'), 30));
}
function task2() {
return new Promise((_, rej) => setTimeout(() => rej(new Error('t2 fail')), 60));
}
async function main() {
try {
const r1 = await task1();
console.log(r1);
const r2 = await task2(); // ここで拒否される
console.log(r2);
} catch (e) {
console.log('main caught:', e.message);
} finally {
console.log('cleanup if needed');
}
}
main();
JavaScript予想:出力の順とどこで捕まる?
実行結果(想定)
t1 ok
main caught: t2 fail
cleanup if needed
解説
task1が成功 →t1 okを出力。task2が拒否されawaitが例外を投げる →catchで受ける。finallyは成功/失敗に関わらず実行される(リソース解放などに使う)。
問題 8 — 演習:あなたが修正者(バグ修正)になる
問題(バグのあるコード)
function readFileSim() {
setTimeout(() => {
// ファイル読み取り処理のシミュレーション
throw new Error('file read failed');
}, 100);
}
try {
readFileSim();
} catch (e) {
console.log('caught:', e.message);
}
JavaScript課題:このコードはファイル読み取りエラーを捕まえられていません。どこをどう直せばいい?(修正案を2つ示してください)
回答(修正案)
- 案 A:コールバック内で try…catch
function readFileSim() {
setTimeout(() => {
try {
throw new Error('file read failed');
} catch (e) {
console.log('handled inside readFileSim:', e.message);
}
}, 100);
}
readFileSim();
JavaScript- 案 B:Promise にして呼び出し側で catch
function readFileSim() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('file read failed'));
}, 100);
});
}
readFileSim()
.catch(e => console.log('caught by caller:', e.message));
JavaScript解説
- 元コードは
setTimeout内のthrowを外側のtryで捕まえられない典型。 - コールバック内でハンドリングするか、Promise に変えて呼び出し側で
.catch()/await+try…catchを使う。
最後に:練習のコツ
- まず「この処理は同期か非同期か?」を自問する習慣をつける。
- 小さな例(上の問題)をコピペして、自分で
throwの場所を変えたりtryの位置を変えたりして、挙動を確認する。 async/awaitを使うとエラーハンドリングが読みやすくなることが多い。Promise の.catch()も忘れずに。- ブラウザのコンソールや Node.js で実行すると、未捕捉エラー(Uncaught Error)の挙動がよく見えて学びになります。
