🪧

JavaScript: あとから個別に await することで並列化しようとすると危ない

2021/10/08に公開

TL;DR

解説

以下は 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: となります。

これは prom1await している間に 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 についてすべてを知りたい方は非常に面白いのでぜひ読んでみてください。

GitHubで編集を提案

Discussion