LoginSignup
7
5

More than 1 year has passed since last update.

forEachの中で非同期処理を行う際、awaitやPromiseを使ってすべての作業が完了するまで待ちたい。

Posted at

はじめに ~ 非同期forEachは一斉に処理が始まる(並列処理?)

非同期処理とforEachについての簡単なプログラムを以下に示す

sample.js
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の後ろに出力を追記する。

sample.js
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 を使ったコードを書いてみよう。

sample2.js
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していく

では forEachPromise.all を使うにはどう記述すればいいか?
私の場合はこうなった。

sample.js
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のためにネスト深くなっちゃったけどやりたいことができたのでヨシ。

間違っている点、こうしたほうが良い点、根本的に別の方法があるよ~などございましたらコメントを頂けると幸いです。

7
5
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5