TL;DR;
Array#forEach(などの)callbackに非同期処理が必要なときに、内部の処理で非同期処理が必要なときに使用すると怪我をするよという話です
どういうこと?
Promiseを返す関数やAPIをloopでよびたいとします
const images = [image1, image2, image3]
images.forEach(async(_image) => {
const imagedata = await createImageBitmap(_image)
// なにか処理をする
})
これが完全に非同期な処理だけならなにも事件はおこらないのですが、内部でfoldingする処理をいれたとします
たとえばwidthの合計とか測定したいとか(そんな需要はほとんどなく、実際は取得したimagedataを処理して全部の結果をとかやるのですが)
const images = [image1, image2, image3]
const allWidth = 0
images.forEach(async(_image) => {
const imagedata = await createImageBitmap(_image)
allWidth += imagedata.width
})
console.log(allWidth)
実行後allWidthはいくつになるでしょうか?
画像の幅の合計値を期待していたかもしれませんが、答は「0」です
何が起こっているのか?
ArrayのforEachのcallbackは関数になりますが、createImageBitmapはPromiseを返します
内容をとりだすためにはawaitすればよいですが、forEachの関数も非同期のfunctionにしないといけなくなります
Promiseの実行は「あとでちゃんとやっておくね」というものなので、Array#forEachがまわりきるまではおよそ、Promiseは未実行の状態になり、forEachの処理はなにもされないままallWidthをconsole.logにながすことになり、答えは0になります
どうすればよかったのか
普通にforを使うシーンです
const main = async() => {
const images = [image1, image2, image3]
const allWidth = 0
for(const i = 0;i < images.lenfth; i++){
const imagedata = await createImageBitmap(images[i])
allWidth += imagedata.width
})
console.log(allWidth)
}
main()
createImageBitmapが順番に評価されるためにはforEachとcallbackでは無理で、順番に処理するためにforで一つのfunctionの内部にいれなくてはいけないということです
asyncのついたfunctionはその内部は同期的ですが、外部からみれば予約された処理です。なので、それを待つためにawait keywordがあるわけですがこのあたりをきちんと理解していないと、こういった罠にかかります(async functionは、functionそのものの実行をPromiseとして一旦返しますよという意味です)
今回はcreateImageBitmapでしたが、fetchでも自作のPromiseでも同じことが当然おこります
まとめ
実際のところ、わたしが罠にかかった経験談です(なんで?って考えて、10分くらいは悩んだ)
これについては、MDNにまでわざわざ記述があります
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
正直、Promiseは非同期処理の記述としては洗練されすぎていて普通の関数と区別がつかないことが多いという優れたデザインなのですが、それゆえにきちんと理解しないと変な挙動に悩まされたりするので、きちんとした理解が必要な仕組みです