
酒本先輩!
第9回で書いたあのデバッグコードなんですけど……
// 3. 非同期エラーの修正版(Promise 化して reject を使用)
app.get('/api/debug-async-error', async (req: Request, res: Response) => {
await new Promise((_, reject) => {
setTimeout(() => {
reject(new Error('非同期エラーの爆弾'));
}, 1000);
});
res.json({ message: '成功' });
});

これ、ちょっとコードが複雑すぎませんか?
new Promise の中で setTimeout を呼んで、しかも引数の最初にあるアンダースコア _ って一体何なんですか!?

ふふ、熊木くん。良いところに気づいたわね!
実はこれ、非同期処理とエラーハンドリングの仕組みをわざと手動で再現した『実験用コード』 だったから複雑に見えたのよ。
今回は番外編として、このコードの裏側にある 2つの謎 をスッキリ解き明かしていくわね!
疑問1:なぜこんなに複雑なコードを書いたのか?

結論から言うと、「古いコールバックAPI(setTimeout)」を「現代の async/await(Promise)」の文脈に無理やり乗せるための橋渡し をしていたからなの。
app.get('/api/debug-async-error', async (req: Request, res: Response) => {
// ① Promise のインスタンスを手動で作る
await new Promise((_, reject) => {
// ② 1秒後にタイマーが発火
setTimeout(() => {
// ③ ここで reject() を呼ぶことで「Promise を失敗(Rejected)状態」にする
reject(new Error('非同期エラーの爆弾'));
}, 1000);
});
res.json({ message: '成功' });
});
なぜわざわざこんなことを?
setTimeout単体ではエラーをキャッチできない:
タイマーのコールバック内で普通にthrow new Error()を書くと、Express の枠組みを飛び出して Node.js サーバー全体がクラッシュ(curl: (52) Empty reply from server)してしまう。reject()を呼ぶことで Promise のエラーに変換:reject(err)を呼ぶと、その Promise は「失敗」の状態になる。awaitとasync関数がエラーを受け取る:awaitしている Promise が失敗すると、async関数の外側に例外として正しく伝播するの。Express 5 はasync関数から飛んできた Promise のエラーを自動で集中エラーハンドラーに送ってくれるから、これで安全に 500 レスポンスを返せるようになったというワケ。
💡 補足:実務ではどう書くの?

実務では setTimeout を手動で new Promise で包む機会はめったにないわ。
DB 操作(Prisma)や外部 API 通信(fetch や axios)など、現代の Node.js ライブラリは最初から Promise を返すように作られているの。
// 実務のリアルなコード例(PrismaでのDB操作)
app.get('/api/users/:id', async (req: Request, res: Response) => {
// prisma.user.findUnique は最初から Promise を返すので、単に await するだけ!
// エラーが起きれば、自動的に Express 5 の集中エラーハンドラーへ飛ぶ
const user = await prisma.user.findUnique({
where: { id: Number(req.params.id) }
});
if (!user) {
res.status(404).json({ error: 'ユーザーが見つかりません' });
return;
}
res.json(user);
});
もし実務で「1秒待つ」ような処理を作りたいなら、共通のユーティリティ関数を作っておくのが定石よ!
// 共通ユーティリティ:指定ミリ秒待つだけのスリープ関数
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
// コントローラー側はすっきり!
app.get('/api/debug-async-error', async (req: Request, res: Response) => {
await sleep(1000); // 1秒待つ
throw new Error('非同期エラーの爆弾'); // async 関数内での throw なので安全!
});
疑問2:Promise((_, reject) のアンダースコア _ って何?

次は、Promise((_, reject) の _ の謎を解明するわね!
new Promise を作るとき、アロー関数の引数には通常 2つの関数 が渡されるわよね。
- 第1引数:
resolve(処理が成功したときに呼ぶ関数) - 第2引数:
reject(処理が失敗したときに呼ぶ関数)
// 本来の書き方(成功も失敗も両方使う場合)
new Promise((resolve, reject) => {
// 成功したら resolve(データ)
// 失敗したら reject(エラー)
});
なぜ _ と書いたのか?

今回のテストコードは「100% 失敗(エラー)させたい」という目的があったわよね。
つまり、処理の中で失敗用の reject しか使わず、成功用の resolve は一度も使わない の。
もし普通に resolve と書くとどうなるか見てみましょう。
// 使わないのに resolve と書いた場合
new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('エラー')); // reject しか使っていない!
}, 1000);
});

TypeScript の設定(noUnusedParameters など)によっては、「resolve という変数を定義したのに一度も使っていません!」 とコンパイラに怒られてしまうことがあるの。
そこで、JavaScript / TypeScript エコシステムでは以下の慣習が使われているわ。
使わない変数や引数の名前には
_(アンダースコア)を付けることで、TS コンパイラやリンターに「使わない変数だけど、順番(位置)合わせのために書いてあるだけだから無視してね!」と伝えることができる。
// 1. 変数名として書いてしまうと...「使ってないよ!」と警告される可能性がある
new Promise((resolve, reject) => { ... })
// 2. アンダースコアにする! ...「あ、使わない位置合わせの引数だな」と安全に無視される
new Promise((_, reject) => { ... })

new Promise の第1引数は「成功用の関数」という位置が決まっているから、第2引数の reject を受け取るためにはどうしても第1引数を書かなきゃいけない。
だから、「使わない第1引数を _ でお茶を濁して、第2引数の reject を受け取っていた」というのが真相よ!
番外編のまとめ
- コードが複雑だった理由: 古いコールバックAPI(
setTimeout)を Promise 化して、Express に安全にエラーを拾わせる実験コードだったから。 - 実務での書き方: DB や外部 API 通信は最初から Promise を返すため、単純に
awaitするだけで OK。 _(アンダースコア)の正体: 位置合わせのために書いている「使わない引数」であることを示す TypeScript/JS の標準的な慣習。

なるほど!
実験用に仕組みを露骨に見せるコードだったから複雑に見えたんですね。
_ の謎も解けてめちゃくちゃスッキリしました!

