ES2017 の forEach() ループでの async/await 処理

async function aSleep(sec) {
    return new Promise(resolve => {
        setTimeout(resolve, sec * 1000)
    })
}

const numbers = [1, 2, 3, 4, 5]

numbers.forEach(async (n) => {
    console.log(`start: ${n}`)
    await aSleep(n)
    console.log(`done: ${n}`)
})

console.log('exit')

JavaScript (ES2017)の async/await について混乱していて、上のコードのように forEach() ループ内で await を含む async 関数を実行すると、その await で処理がブロックされて逐次ループ処理と変わらないと思い込んでいたのだが、勘違いだった。

上記コードの Chrome での実行結果ログ:

start: 1
start: 2
start: 3
start: 4
start: 5
exit
done: 1
done: 2
done: 3
done: 4
done: 5

つまり forEach() の部分は以下のように書いたのと同義となり、async 関数が終了してから次のループを処理するのではなく、async 関数の終了を待たずに(async 関数戻り値の Promise を無視して)ループを回し、各 async 関数は並行実行される。

async function f(n) {
    console.log(`start: ${n}`)
    await aSleep(n)
    console.log(`done: ${n}`)
}

for (let i = 0; i < numbers.length; i++) {
    const n = numbers[i]
    f(n).then(() => {
        // do nothing
    })
}
Last updated on September 11, 2018