【Node.js】for文内の非同期処理の完了を待って、次を実行する

APIリクエストなど非同期処理をfor文で何度か繰り返し、それぞれの完了を待ってから次に進みたい時に。

例えば、こんなfor文を書いた時に、

var sum = 0

for (let i = 0; i <= 10; i++) {
    exampleFunc(i).then(res => sum += res)
}

console.log(sum)

function exampleFunc(index) {
    return new Promise((resolve, reject) => {
        // 何か重い処理をして、resultを返す
        resolve(index)
    })
}

このまま動かすと、for文は「i=1の時、exampleFunc実行→レスポンスを待たずに次へ→i=2の時、exampleFunc実行→…」という動作をするため、

console.log(sum)で表示される値が不安定(APIのレスポンス速度によって、集計されるタイミングがバラバラになる)

正常な動作として、「i=1の時、exampleFunc実行→レスポンスを受け取る→i=2の時、exampleFunc実行→レスポンスを受け取る→…」となってほしい。

方法1: async/awaitで確実に1件ずつ待つ(推奨)

最もシンプルで意図通りの動作をするのは、for...ofawaitを組み合わせる方法です。

async function main() {
    var sum = 0

    for (let i = 0; i <= 10; i++) {
        const res = await exampleFunc(i)
        sum += res
    }

    console.log(sum)
}

function exampleFunc(index) {
    return new Promise((resolve, reject) => {
        // 何か重い処理をして、resultを返す
        resolve(index)
    })
}

main()

awaitを使うと、そのPromiseがresolveされるまでfor文の次のループに進まないため、1件ずつ確実に完了を待ってから次を実行できます。通常の同期処理と同じ感覚で書けるのが利点です。

方法2: Promise.allで全件の完了を待つ(並列実行でよい場合)

「1件ずつ順番に」ではなく「全部同時に実行して、全て終わったら次に進みたい」場合は、Promise.allの方が高速です。

var tasks = []
var sum = 0

for (let i = 0; i <= 10; i++) {
    tasks.push(exampleFunc(i).then(res => sum += res))
}

Promise.all(tasks).then(() => {
    console.log(sum)
})

function exampleFunc(index) {
    return new Promise((resolve, reject) => {
        // 何か重い処理をして、resultを返す
        resolve(index)
    })
}

Promise.allPromiseの配列を渡すと、配列内の全てがresolveされ次第、then内が実行されます。for文の実行自体は待たずに全て同時に開始されるため、方法1(await)より高速ですが、「1件ずつ順番に」処理したい場合には向きません。

まとめ

  • 1件ずつ順番に完了を待ちたい → for...of + await(方法1)
  • 順番は問わず、全件終わったタイミングだけ知りたい → Promise.all(方法2、より高速)
タイトルとURLをコピーしました