はじめに ~ 非同期forEachは一斉に処理が始まる(並列処理?)
非同期処理とforEachについての簡単なプログラムを以下に示す
const array = [1, 2, 3, 4, 5]
array.forEach(n => {
setTimeout(() => {
console.log(`${n}秒待ちました。`)
}, n*1000);
});
これを実行すると以下のように出力される。
1秒待ちました。
2秒待ちました。
3秒待ちました。
4秒待ちました。
5秒待ちました。
さて、この出力は1秒おきに1行ずつ表示されるため、5種類の setTimeout(() => {})
が同時に実行されていることが分かる。(正確には同時ではないが)
つまづいた点 ~ 処理が終わってないのにforEachがスルーされている?(←勘違い)
とりあえずforEachの後ろに出力を追記する。
const array = [1, 2, 3, 4, 5]
array.forEach(n => {
setTimeout(() => {
console.log(`${n}秒待ちました。`)
}, n*1000);
});
console.log('forEach完了!')
出力はこうなる。
forEach完了!
1秒待ちました。
2秒待ちました。
3秒待ちました。
4秒待ちました。
5秒待ちました。
違う、そうじゃない、非同期処理が終わってから知らせてほしいんだと小一時間頭を悩ませた。
しかし実はこの出力、何も間違っていなかったのである。
今回のコードの場合、forEachのそれぞれのステップにおいて setTimeout(() => {})
が実行された時、別の場所で「1秒待って出力」という処理は行われているが、そのforEachのステップ自体は完了している。
したがって、 forEach完了!
は本当にforEachが終了したからこそ出力されたのだ。
解決に向けて ~ Promise.all の勉強
ではどうすればforEach内のすべての非同期処理が完了したことを検知することができるのか。
答えは Promice.all
である。
5回分の非同期処理をPromiseを使って行い、すべてのPromiseが完了したことを Promice.all
で検知することができる。
まず簡単な Promice.all
を使ったコードを書いてみよう。
const Promise1 = new Promise((resolve) => {
setTimeout(() => {
console.log(`1秒待ちました。`)
resolve();
}, 1000);
})
const Promise2 = new Promise((resolve) => {
setTimeout(() => {
console.log(`2秒待ちました。`)
resolve();
}, 2000);
})
Promise.all([Promise1, Promise2])
.then(() => {
console.log('Promise 2つとも完了')
})
結果
1秒待ちました。
2秒待ちました。
Promise 2つとも完了
2つのPrimiseを用意し Promise.All
に配列として渡すことで、 Promise.all
は全て終わった後に .then
の処理を実行する。
解決策 ~ Primises配列にpushしていく
では forEach
で Promise.all
を使うにはどう記述すればいいか?
私の場合はこうなった。
const array = [1, 2, 3, 4, 5]
var Promises = []
array.forEach(n => {
Promises.push(
new Promise((resolve) => {
setTimeout(() => {
console.log(`${n}秒待ちました。`)
resolve();
}, n*1000);
})
)
});
console.log('forEach完了!')
Promise.all(Promises)
.then(() => {
console.log('forEach(中の非同期処理も含めて)完了!')
})
結果
forEach完了!
1秒待ちました。
2秒待ちました。
3秒待ちました。
4秒待ちました。
5秒待ちました。
forEach(中の非同期処理も含めて)完了!
まとめ
なんだかたかがforEachのためにネスト深くなっちゃったけどやりたいことができたのでヨシ。
間違っている点、こうしたほうが良い点、根本的に別の方法があるよ~などございましたらコメントを頂けると幸いです。