🪧
JavaScript: あとから個別に await することで並列化しようとすると危ない
TL;DR
- 並列化したいときは
Promise.all(...)
を使います。 - 複数の Promise を取り扱うときは上記含めた、
.race(...)
,.any(...)
,.allSettled(...)
の使用を検討します。
解説
以下は Deno と Node で動かして試せるようにしています。
// 実際には API 叩いたりとか
const get = (a, b) => new Promise((r) => setTimeout(() => r(a + b), 100));
// 実際には例外が投げられうる処理
const err = () => Promise.reject(new Error("err"));
async function main() {
const prom1 = get(1, 2);
const prom2 = err();
const prom3 = get(2, 3);
await prom1;
await prom2;
await prom3;
}
main().catch((e) => console.log("catch!", e));
これを実行すると、Node では UnhandledPromiseRejectionWarning
, Deno では Uncaught (in promise) Error:
となります。
これは prom1
を await
している間に prom2
が例外を投げて Unhandled Error になりますが、その時点では .catch
などされていないので Unhandled Error のままになり、実行コンテキストによってはプロセスやワーカーを終了させかねません。
以下のように修正することで正しくハンドルできます。
async function main () {
const prom1 = get(1, 2);
const prom2 = err();
const prom3 = get(2, 3);
await Promise.all([prom1, prom2, prom3]);
}
catch! Error: err
...
経緯
OSS でこの書き方を見かけることがありました。一見良さそうで直す能動的な理由がないために放置でいいかと思っていましたが、問題が発生しうることがわかり、今後簡単に参照できるように記事にしました。
この問題は https://zenn.dev/qnighy/articles/0aa6ec47248d80 でも触れられています。 Promise
についてすべてを知りたい方は非常に面白いのでぜひ読んでみてください。
Discussion