JavaScriptのforEachでは非同期処理を待ってくれなくてハマった
はじめに
昨日公開したGatsbyプラグインgatsby-plugin-satorare
を作っているときにハマったのでメモしておく。ちなみに開発ブログは以下。
どこでハマったか
Nodeの配列内のそれぞれの要素に対しOG画像を生成する処理をした際に、forEach
を利用した。
nodes.forEach(async (node) => {
const image = await generateImage()
...
})
これをビルドして動作確認していたところ、以下のようなエラーが出た。
BindingError: Expected null or instance of Node, got an instance of Node
エラーの原因はさっぱりだったが、エラー文で検索をかけると以下のZennのScrapを見つけた。
この中ではvercel/satori
を使用してSVG画像を生成する処理をPromise.all
を使って並列で処理しようとしたところ上記のエラーが出ており、順次実行するようにすることでエラーを解消できたとのこと。
自分は並列処理をしているつもりはなかったが、forEach
を使うことでそうなってしまっているのかと思って調べてみると記事のタイトルの通りforEach
は非同期処理を待ってくれないとのことだった。
メモ: forEach は同期関数を期待します。
forEach はプロミスを待ちません。forEach のコールバックとしてプロミス (または非同期関数) を使用する場合は、その意味合いを理解しておくようにしてください。
解決法
forEach
の代わりにfor
を使うことで解決した。これで画像処理が逐次的に処理されるようになり、上記のエラーは出なくなった。
for (const node of nodes) {
const image = await generateImage()
...
}
おわり
順番を待つ必要がない処理であればforEach
を使っても良いと思うし、今回の画像処理などはまさに使う場面だろう。ただ、たまたまライブラリの仕様上それが許されず、たまたまforEach
のこの特性を知ることになった。
二度と引っかかりたくない。