背景
これやってみると確かにそうなった。ツイートの内容の通りの対応でよいとは思う。
残念ながらこれは辞めたほうがいい
— acomagu (@acomagu) 2024年12月6日
Promise は作ったらすぐに await するか .catch をつけておかないと、エラーが throw されたときに Unhandled Rejection になって Node がプロセスごと落ちる可能性があるので
↓ try ... catch しても catch されない例 https://t.co/K9UiPThzdr pic.twitter.com/niIhhMaNjq
そもそも大本の書き方は正しくなくて、Promise.allなどですべて成功するか、一部失敗するかをまとめて待つべき、というのは体が覚えている。なんで覚えているかはよくわからない(多分jQuery.Deferredで色々痛い目を見ていた気がする)
それはそれとして、試してみるとNodeJSだけそうなるようだった。それがちょっとよくわからなかったのでメモ。
要約
結論をいえば以下の通り。
- Unhandled Rejectionという「UnhandleかつReject状態のPromiseがあるときの振る舞い」がある
- NodeJSのUnhandled Rejectionの扱い方がちょっと特殊っぽい
process.on('unhandledRejection')
ハンドラを設定すると問題のコードでもcatchしてくれつつ、警告は出るので区別できなくなることはないです
- とはいえ、そもそもUnhandled Rejectionが起きるようなコードは悪いので、なぜ起きるかを理解し、起きないようなコードをかきましょう
詳しくは詳しい方々がたっぷりねっとり解説されているので読みましょう。
yosuke-furukawa.hatenablog.com
なお、私は心が折れて全部読めなかった。以降はハルシネーションが起きている可能性があると思って読んでください。
調べてた時のログ
問題のコードを書く
元ネタからsleep関数を足したりログ足したりしているけど、概ね一緒だと思う。
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); } async function getUser() { await sleep(10); } async function getPost() { throw new Error("ERROR"); } try { const userPromise = getUser(); const postPromise = getPost(); const user = await userPromise; const post = await postPromise; console.log("Done"); } catch (e) { console.log("catch!", e); } finally { console.log("finally"); } console.log("end");
NodeJS (v22) →catchされない
Node v22だと catchが行われていないようにみえる。
PS T:\Temp> node .\test1.js file:///T:/Temp/test1.js:10 throw new Error("ERROR"); ^ Error: ERROR at getPost (file:///T:/Temp/test1.js:10:11) at file:///T:/Temp/test1.js:15:25 at ModuleJob.run (node:internal/modules/esm/module_job:271:25) at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:547:26) at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:116:5) Node.js v22.12.0
Bun →catchされる
一応、Bunでも試してみた。ログが変だがちゃんと動作した。このログを取った直後は理解できていなかったが、おそらく変な理由はUnhandled Rejectionの処理とcatchの処理が動いているからだと思う。
テキストのログを表示
PS T:\Temp> bun run .\test1.js 5 | async function getUser() { 6 | await sleep(10); 7 | } 8 | 9 | async function getPost() { 10 | throw new Error("ERROR"); ^ error: ERROR at T:\Temp\test1.js:10:11 at getPost (T:\Temp\test1.js:9:23) at T:\Temp\test1.js:15:25 catch! 5 | async function getUser() { 6 | await sleep(10); 7 | } 8 | 9 | async function getPost() { 10 | throw new Error("ERROR"); ^ error: ERROR at T:\Temp\test1.js:10:11 at getPost (T:\Temp\test1.js:9:23) at T:\Temp\test1.js:15:25 finally end Bun v1.1.38 (Windows x64)
ブラウザ →catchされる
ブラウザで挙動を見るために、こんなHTMLファイルを書く。
<!DOCTYPE html> <html> <head> <title>Home</title> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width" /> <script type="module"> function sleep(ms) { return new Promise(r => setTimeout(r, ms)); } async function getUser() { await sleep(10); } async function getPost() { throw new Error("ERROR"); } try { const userPromise = getUser(); const postPromise = getPost(); const user = await userPromise; const post = await postPromise; console.log("Done"); } catch (e) { console.log("catch!", e); } finally { console.log("finally"); } console.log("end"); </script> </head> <body> <main> <h1>see console</h1> </main> </body> </html>
firefox 133.0 | Chrome 131.0.6778.86 |
---|---|
NodeJSが変にみえてしまう。「だってtryで括ってるし、後とはいえawaitで待ってるじゃん!他の環境はまともなのになんで!」と。
多分私のPromiseの理解が足りなさそうだったので調べなおすことにした。
async/awaitを使わずPromiseで書いてみる
厳密には一致していないとは思うけど、こんな感じになっていると思われる。(変数のスコープとか細かい違いは再現できていないが、今回あまり関係ないので。)
一応、async/awaitのときと同じような動きをしていたので、動き的には近いはず。
new Promise((resolve,reject) => { const userPromise = getUser(); const postPromise = getPost(); userPromise.then((user) => { return postPromise.then((post) => { console.log("Done"); resolve(); }); }).catch((e) => { console.log("catch!", e); reject(e); }).then(() => { console.log("end"); }); });
要は、asyncはnew Promise、awaitの後の処理がthenになっている、みたいに読み替えればよい。
こうやってみると少し雰囲気が変わってくる。postPromise がcatchに関わらない状態がありそう、というのとか。
Promiseの動き
v8のコードを眺めながら追いかけてみる。雰囲気だけ。
- new Promiseをすると、渡したコールバックはすぐに処理される模様。resolve/rejectを呼ぶまではそのPromiseはpending状態になる。
- Promiseのthenを呼び出すと、
- まだPending状態なら、PromiseにPromiseReactionとして渡したコールバックを登録(コメントにある通り、fullfilled/rejected状態になったら処理される)
- それ以外(Fullfilled/Rejected状態)であれば、渡したコールバックがマイクロタスクとしてキューに追加される。
今回のコードではgetUserのコード上、10ミリ秒待たせているのでpending状態になり、10ミリ秒後にfullfilled状態となる。それまではuserPromise.thenのコールバックを呼ぶことがないため、それまではpostPromiseのthenが呼ばれることはない。つまり少なくとも10ミリ秒の間 postPromiseはUnhandleな状態になる。
なので、getUserのコードが処理が終わるまでの10ミリ秒の間に、postPromiseの非同期処理を処理しようとしてthrow new Error
が発生してしまうと、UnhandleかつRejectedな状態になるため、ランタイムがこれを検知してUnhandled Rejectionが発生してしまうらしい。NodeJSではtry-catchが無視され。
これをステップバイステップで見てみるとこんな感じだと思う。
- new Promiseのコールバックを処理する
- getUserのコールバックをマイクロタスクキューに追加する(タスク1)
- getPostのコールバックをマイクロタスクキューに追加する(タスク2)
- userPromiseのthen、catchを処理し、コールバックを設定
- 呼び出し元に返る
- マイクロタスクキューからタスク1を取り出し処理する
- await sleep(10)があるので、この処理が終わるまで待つ必要があるため中断(pending)
- マイクロタスクキューからタスク2を取り出し処理する
- throw new Error(...) が発生する
- →Unhandled Rejectionが発生
そして、getUserのawait sleep(10)
をコメントアウトすると、以下のような動きになっていると思われる。
- マイクロタスクキューからタスク1を取り出し処理する
- 処理が終わるので、fullfiled状態になる
- userPromise.thenに渡したコールバック処理を行う
- postPromise.thenのコールバック処理を設定する(postPromiseがUnhandle -> Handleになる)
- マイクロタスクキューからタスク2を取り出し処理する
- throw new Error(...) が発生する
- →Handle状態なので、Unhandled Rejectionにならない
実際、NodeJSでもUnhandled Rejectionにならないので、ちゃんとcatchが処理される。
しかし、実際はこんなシンプルではなく、「getUserが20ミリ秒後にfullfilledになり、getPostが30ミリ秒後にRejectedになる」となれば、ちゃんとtry-catchが動くし、「getUserが20ミリ秒後にfullfilledになり、getPostが10ミリ秒後にRejectedになる」となるとtry-catchをすり抜ける、という事態になる。
この振る舞いの変化に悩んでみたいですか?体験してみましょう。
https://jsbin.com/tuvujudepa/1/edit?js,console,output
何度か実行してみてください。unhandledrejection
ハンドラが処理されているときが、NodeJSではプロセスダウンしているようなもの。
NodeJSはError-Likeなオブジェクトでrejectされると、Unhandle Rejectionが起きてもわかりにくくなる
それはそれとして、NodeJSだとcatchが行われていないようにみえるのは困る。
この疑問をツイートしていたら「nodeでは次のイベントループに進むまでにerror handlerがないとunhandled rejectionになる」とtyage先生に教えていただいた。
でもエラー出力を見てもそれらしい文言にならないのはなぜなのか、というのを調べた。
実装上、Errorっぽいオブジェクト(objectかつstackプロパティを持っている)がthrowされていたら、それをそのまま使う、というふるまいをしている。そのせいでエラーの情報は出ているがUnhandled Rejectionに関するメッセージが出ない状態になっているらしい。
実際に、 throw new Error("ERROR");
ではなく throw "ERROR"
とすると、その様子がわかる。
async function getPost(){ - //throw new Error("ERROR"); + throw "ERROR"; }
PS T:\Temp> node test3.mjs node:internal/process/promises:392 new UnhandledPromiseRejection(reason); ^ UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "ERROR". at throwUnhandledRejectionsMode (node:internal/process/promises:392:7) at processPromiseRejections (node:internal/process/promises:475:17) at process.processTicksAndRejections (node:internal/process/task_queues:106:32) { code: 'ERR_UNHANDLED_REJECTION' } Node.js v22.12.0
お、ERR_UNHANDLED_REJECTION
とはわかりやすいですね!まぁどこで問題が起きたかわからないので頭を抱えることになると思いますけど。
あとそもそも、人間がエラーを発生させるときはたいていthrow new Error(...)
をベースにすると思うので、これに遭遇することは少ないでしょう。つまり、Errorが発生しているがcatchされないという事象に引っかかることが多いはず。
Unhandled Rejectionとかよくわからん。どうでもいいから、NodeJSでcatchの処理を保証してほしい
2つの方法がありそう。以下のいずれか。
process.on('unhandledRejection')
ハンドラを設定する--unhandled-rejections
フラグをwarn
かwarn-with-error-code
にして起動
個人的な感想。NodeJSの運用はあまり詳しくないので適当です。
process.on('unhandledRejection', (reason, promise) => { ... ; })
を実装しておくとよさそう。その場合、--unhandled-rejections
フラグはstrict以外ほぼ意味がなくなる(警告の出方が微妙に違うだけになる)- unhandledRejectionハンドラを書けない場合は
warn
かwarn-with-error-code
にすると、とりあえずcatchはされるようになる - exit codeはunhandledRejectionハンドラを登録している場合、その中で
process.exit(123)
とすればコントロールできるが、catchの処理がされないので一長一短感。要らないかな?
このあたりの挙動を表でまとめてみたけど、わかりにくい。
process.on('unhandledRejection') | --unhandled-rejections |
exit code | catchの処理 | Unhandled rejectionの警告出力 | unhandledRejectionイベント処理 |
---|---|---|---|---|---|
なし | none | 0 | 〇 | × | - |
なし | warn | 0 | 〇 | 〇 | - |
なし | warn-with-error-code | 1 | 〇 | 〇 | - |
なし | strict | 1 | × | × | - |
なし | throw (default) | 1 | × | × | - |
あり | none | 0 | 〇 | 〇 | 〇 |
あり | warn | 0 | 〇 | 〇 | 〇 |
あり | warn-with-error-code | 0 | 〇 | 〇 | 〇 |
あり | strict | 1 | × | × | × |
あり | throw (default) | 0 | 〇 | 〇 | 〇 |
あり(exitあり) | none | 123 | × | × | 〇 |
あり(exitあり) | warn | 123 | × | × | 〇 |
あり(exitあり) | warn-with-error-code | 123 | × | × | 〇 |
あり(exitあり) | strict | 1 | × | × | × |
あり(exitあり) | throw (default) | 123 | × | × | 〇 |
確認したときのログを表示
PS T:\Temp> node --unhandled-rejections=none test1.mjs catch! Error: ERROR at getPost (file:///T:/Temp/test1.mjs:12:11) at file:///T:/Temp/test1.mjs:18:25 at ModuleJob.run (node:internal/modules/esm/module_job:271:25) at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:547:26) at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:116:5) finally end (node:46804) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1) (Use `node --trace-warnings ...` to show where the warning was created) PS T:\Temp> echo $LASTEXITCODE 0 PS T:\Temp> node --unhandled-rejections=warn test1.mjs (node:24844) UnhandledPromiseRejectionWarning: Error: ERROR at getPost (file:///T:/Temp/test1.mjs:12:11) at file:///T:/Temp/test1.mjs:18:25 at ModuleJob.run (node:internal/modules/esm/module_job:271:25) at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:547:26) at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:116:5) (Use `node --trace-warnings ...` to show where the warning was created) (node:24844) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1) catch! Error: ERROR at getPost (file:///T:/Temp/test1.mjs:12:11) at file:///T:/Temp/test1.mjs:18:25 at ModuleJob.run (node:internal/modules/esm/module_job:271:25) at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:547:26) at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:116:5) finally end (node:24844) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1) PS T:\Temp> echo $LASTEXITCODE 0 PS T:\Temp> node --unhandled-rejections=warn-with-error-code test1.mjs (node:15608) UnhandledPromiseRejectionWarning: Error: ERROR at getPost (file:///T:/Temp/test1.mjs:12:11) at file:///T:/Temp/test1.mjs:18:25 at ModuleJob.run (node:internal/modules/esm/module_job:271:25) at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:547:26) at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:116:5) (Use `node --trace-warnings ...` to show where the warning was created) (node:15608) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1) catch! Error: ERROR at getPost (file:///T:/Temp/test1.mjs:12:11) at file:///T:/Temp/test1.mjs:18:25 at ModuleJob.run (node:internal/modules/esm/module_job:271:25) at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:547:26) at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:116:5) finally end (node:15608) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1) PS T:\Temp> echo $LASTEXITCODE 1 PS T:\Temp> node --unhandled-rejections=strict test1.mjs file:///T:/Temp/test1.mjs:12 throw new Error("ERROR"); ^ Error: ERROR at getPost (file:///T:/Temp/test1.mjs:12:11) at file:///T:/Temp/test1.mjs:18:25 at ModuleJob.run (node:internal/modules/esm/module_job:271:25) at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:547:26) at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:116:5) Node.js v22.12.0 PS T:\Temp> echo $LASTEXITCODE 1 PS T:\Temp> node --unhandled-rejections=throw test1.mjs file:///T:/Temp/test1.mjs:12 throw new Error("ERROR"); ^ Error: ERROR at getPost (file:///T:/Temp/test1.mjs:12:11) at file:///T:/Temp/test1.mjs:18:25 at ModuleJob.run (node:internal/modules/esm/module_job:271:25) at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:547:26) at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:116:5) Node.js v22.12.0 PS T:\Temp> echo $LASTEXITCODE 1
unhandledRejectionあり
PS T:\Temp> node --unhandled-rejections=none test1.mjs on unhandledRejection Error: ERROR at getPost (file:///T:/Temp/test1.mjs:17:11) at file:///T:/Temp/test1.mjs:24:25 at ModuleJob.run (node:internal/modules/esm/module_job:271:25) at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:547:26) at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:116:5) catch! Error: ERROR at getPost (file:///T:/Temp/test1.mjs:17:11) at file:///T:/Temp/test1.mjs:24:25 at ModuleJob.run (node:internal/modules/esm/module_job:271:25) at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:547:26) at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:116:5) finally end (node:30736) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1) (Use `node --trace-warnings ...` to show where the warning was created) PS T:\Temp> echo $LASTEXITCODE 0 PS T:\Temp> node --unhandled-rejections=warn test1.mjs on unhandledRejection Error: ERROR at getPost (file:///T:/Temp/test1.mjs:17:11) at file:///T:/Temp/test1.mjs:24:25 at ModuleJob.run (node:internal/modules/esm/module_job:271:25) at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:547:26) at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:116:5) (node:37820) UnhandledPromiseRejectionWarning: Error: ERROR at getPost (file:///T:/Temp/test1.mjs:17:11) at file:///T:/Temp/test1.mjs:24:25 at ModuleJob.run (node:internal/modules/esm/module_job:271:25) at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:547:26) at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:116:5) (Use `node --trace-warnings ...` to show where the warning was created) (node:37820) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1) catch! Error: ERROR at getPost (file:///T:/Temp/test1.mjs:17:11) at file:///T:/Temp/test1.mjs:24:25 at ModuleJob.run (node:internal/modules/esm/module_job:271:25) at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:547:26) at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:116:5) finally end (node:37820) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1) PS T:\Temp> echo $LASTEXITCODE 0 PS T:\Temp> node --unhandled-rejections=warn-with-error-code test1.mjs on unhandledRejection Error: ERROR at getPost (file:///T:/Temp/test1.mjs:17:11) at file:///T:/Temp/test1.mjs:24:25 at ModuleJob.run (node:internal/modules/esm/module_job:271:25) at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:547:26) at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:116:5) catch! Error: ERROR at getPost (file:///T:/Temp/test1.mjs:17:11) at file:///T:/Temp/test1.mjs:24:25 at ModuleJob.run (node:internal/modules/esm/module_job:271:25) at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:547:26) at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:116:5) finally end (node:29420) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1) (Use `node --trace-warnings ...` to show where the warning was created) PS T:\Temp> echo $LASTEXITCODE 0 PS T:\Temp> node --unhandled-rejections=strict test1.mjs file:///T:/Temp/test1.mjs:17 throw new Error("ERROR"); ^ Error: ERROR at getPost (file:///T:/Temp/test1.mjs:17:11) at file:///T:/Temp/test1.mjs:24:25 at ModuleJob.run (node:internal/modules/esm/module_job:271:25) at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:547:26) at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:116:5) Node.js v22.12.0 PS T:\Temp> echo $LASTEXITCODE 1 PS T:\Temp> node --unhandled-rejections=throw test1.mjs on unhandledRejection Error: ERROR at getPost (file:///T:/Temp/test1.mjs:17:11) at file:///T:/Temp/test1.mjs:24:25 at ModuleJob.run (node:internal/modules/esm/module_job:271:25) at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:547:26) at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:116:5) catch! Error: ERROR at getPost (file:///T:/Temp/test1.mjs:17:11) at file:///T:/Temp/test1.mjs:24:25 at ModuleJob.run (node:internal/modules/esm/module_job:271:25) at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:547:26) at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:116:5) finally end (node:19112) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1) (Use `node --trace-warnings ...` to show where the warning was created) PS T:\Temp> echo $LASTEXITCODE 0
ハンドラあり + ハンドラでprocess.exit(123)
PS T:\Temp> node --unhandled-rejections=none test1.mjs on unhandledRejection Error: ERROR at getPost (file:///T:/Temp/test1.mjs:18:11) at file:///T:/Temp/test1.mjs:25:25 at ModuleJob.run (node:internal/modules/esm/module_job:271:25) at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:547:26) at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:116:5) PS T:\Temp> echo $LASTEXITCODE 123 PS T:\Temp> node --unhandled-rejections=warn test1.mjs on unhandledRejection Error: ERROR at getPost (file:///T:/Temp/test1.mjs:18:11) at file:///T:/Temp/test1.mjs:25:25 at ModuleJob.run (node:internal/modules/esm/module_job:271:25) at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:547:26) at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:116:5) PS T:\Temp> echo $LASTEXITCODE 123 PS T:\Temp> node --unhandled-rejections=warn-with-error-code test1.mjs on unhandledRejection Error: ERROR at getPost (file:///T:/Temp/test1.mjs:18:11) at file:///T:/Temp/test1.mjs:25:25 at ModuleJob.run (node:internal/modules/esm/module_job:271:25) at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:547:26) at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:116:5) PS T:\Temp> echo $LASTEXITCODE 123 PS T:\Temp> node --unhandled-rejections=strict test1.mjs file:///T:/Temp/test1.mjs:18 throw new Error("ERROR"); ^ Error: ERROR at getPost (file:///T:/Temp/test1.mjs:18:11) at file:///T:/Temp/test1.mjs:25:25 at ModuleJob.run (node:internal/modules/esm/module_job:271:25) at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:547:26) at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:116:5) Node.js v22.12.0 PS T:\Temp> echo $LASTEXITCODE 1 PS T:\Temp> node --unhandled-rejections=throw test1.mjs on unhandledRejection Error: ERROR at getPost (file:///T:/Temp/test1.mjs:18:11) at file:///T:/Temp/test1.mjs:25:25 at ModuleJob.run (node:internal/modules/esm/module_job:271:25) at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:547:26) at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:116:5) PS T:\Temp> echo $LASTEXITCODE 123
Unhandled RejectionはESLintとかの静的解析で防げないか?
防げない。
@typescript-eslint/no-floating-promises
では投げっぱなしのPromiseを検知はできるのだが、今回のコードはフロー上ちゃんと変数に入れているので検知はできない模様。試したがひっかからなかった。
Issueには上がったようだが、対応しない模様。
メンテナーさんの意見もごもっとも。