Array.prototype.map 内で非同期関数を走らせて、配列の要素ごとに処理する +α
ちょっと Promise
や async/await
に躓いたのでメモとして書き残しておく。
間違った処理
当初は以下のような処理を書いていた。ある配列の全要素に対し一つ一つ処理を実行し、その結果に対して判定処理をしたかった。
let arr = [] const timeout = (ms) => { return new Promise(resolve => setTimeout(() => { console.log("waiting...") resolve() }, ms)) } // failed pattern const exe = async () => { await [1,2,3].map(async item => { console.log(item) await timeout(100) item > 2 && arr.push(item) }) console.log(arr) if (arr.length === 1) console.log('ok') } exe()
timeout
関数が実際には API コール(fetch()
の実行など)だったりするが、今回は setTimeout
するだけのもの。最初はこう書いていて、await timeout(100)
のところで毎回止まってくれることを想定していたが、結果は以下。
1 2 3 [] waiting... waiting... waiting...
arr.push(item)
のところで配列に値がセットされているのだが、そもそも await timeout(100)
のところで処理が止まっておらず、先に map()
の処理からメインスレッドに戻ってしまっていた。ちなみに上記の map
を forEach
に変更しても同様。
修正版
これを以下のように修正したらうまく動作した。
const exe = async () => { - await [1,2,3].map(async item => { + await Promise.all([1,2,3].map(async item => { console.log(item) await timeout(100) item > 2 && arr.push(item) - }) + })) console.log(arr) if (arr.length === 1) console.log('ok') } exe()
これの実行結果は
1 2 3 waiting... waiting... waiting... [ 3 ] ok
となり、期待通りの動作。元々の await [1,2,3].map()
の処理の戻り値を変数に入れて console.log
で見てみると
[ Promise { <pending> }, Promise { <pending> }, Promise { <pending> } ]
の様に Promise
の配列になっている事がわかる。なので、これを Promise.all
で単一の Promise
として処理してしまえば良かった。
参考: Promise.all()
余談
Array.prototype.filter
のコールバックに async
を付けると本来の挙動をしなくなる。百聞は一見に如かないので、以下のコードを見てほしい。(最初のコードの map
を filter
に変えただけ)
// ok const arr = [1,2,3].filter(item => item > 2) console.log(arr) // => [3] // ng const arr = [1,2,3].filter(async item => item > 2) console.log(arr) // => [1,2,3]
勉強になった。ではでは(=゚ω゚)ノ