forEach()
でも他の配列メソッドでも、async
は正しく動く。ただ、配列メソッドに渡した関数が要素順に実行されなくなり、混乱が生じやすい。なるべくなら使わない方が無難だ。MDNにも非推奨と書いてある。
最小限のコードで挙動を見る
await
しているのだから0 1 2 3 4
になって欲しいところだが、この場合の出力結果は実行するたびに異なる。
同期的に処理されない理由
配列メソッドは、端から順番に(=前の要素に適用された関数が終了した後に)次の要素を走査していくのが暗黙の了解になっている。ただ、async
をつけた関数は精神と時の部屋みたいになる。具体的には、内側と外側と時間がズレる。
これを配列メソッドに渡すと、配列の要素ごとに精神と時の部屋ができあがり、それぞれの関数の終了タイミングが予測困難になってしまう。
例えば配列.map(async ()=> await 非同期関数())
みたいにすると、まず精神と時の部屋の配列、つまりPromise
の配列ができ上がり、そこから処理の軽い順に実行が終わっていく。
値が返される配列メソッドならまだやりようはあるが、forEach()
のように投げっぱなしのメソッドだと制御が面倒だ。これが理由で、配列メソッドの中でも特にforEach()
にasync
キーワードをつけた関数を渡すのは避けたほうが良い、とされている。
※以下のサンプルコードでは、簡便のためにawait
キーワードをトップレベルに記述している。これはモジュールの場合にしか機能しない。非モジュールで利用する場合は、async
関数の中で使う必要がある。
代案1:Array.fromAsync()
複数の非同期関数を順番に実行したい場合、Array.fromAsync()という静的メソッドをを使うのが現状もっとも手軽な方法かと思う。
代案2:配列.reduce()
配列.reduce()
メソッドは、一つ前の結果を関数に渡してくれる。非同期関数の場合はPromiseが渡されるため、これをawait
することで順次処理を実現できる。
代案3:for…of
複数の非同期関数を順次実行しようとするのが問題の根幹である。ひとつのasync
の中でawait
すれば問題は解決する。
おしまい。