非同期関数のループ処理 - Part1（forEach + async/await）

##処理方法とその特徴
Array.prototype.forEach()のコールバック関数に非同期の関数であるデータベースクエリなどを入れてループ処理を行った際の挙動が気になったため、いくつかのパターン別にサンプルコードを作成して確認してみました。今回は、Part1として、forEach + async/awaitから見ていきます。

その特徴ですが、非同期関数のループ処理においてforEach + async/awaitを使用すると、awaitが効かず、forEachが爆走します。

##ループ処理の内容
フェイク非同期関数（fakeDatabaseQuery）を作成し、forEachのコールバックとしてフェイク非同期関数を100回実行するとどのような挙動になるのか、試してみました。

1. 関数processArrayの中にあるforEachのループ処理によって、関数fakeDatabaseQuery()が連続して実行されますが、forEachの場合はasync/awaitによるサスペンドが効かないため、promiseオブジェクトが連続的に作成されるものの、それぞれのpromiseはresolveされた順番で次のプロセスに渡されます。

2. 関数fakeDatabaseQuery()の中でpromiseオブジェクトを生成しますが、setTimeoutは、ランダムなelapseの値を使って、resolveする際の遅延を作り出しています。そのため、連続してpromiseが生成されるのですが、elapseの値がランダムであるため、resolveされる順番がバラバラとなります。

3. awaitは常にpromiseオブジェクトを待ち受ける役割となります。今回は、awaitによるfakeDatabaseQuery()のサスペンドとレジュームは効きませんが、promiseがresolveされ次第（=>この状態をfulfilledといいます）、awaitはpromiseから値を取り出して、変数objにその値を代入します。

4. awaitによってpromiseオブジェクトから取り出された値は、次のプロセスとして、fakeSearchResult.push()に渡され、その渡された順番でアレイfakeSearchResultに詰め込まれていきます。

5. ただし、その後ソートしているので、最終的には、当初のループ処理の順番とおりになっています。

##まとめ
プログラムを見ると、一見、async/awaitによりarrayのアイテムをひとつずつ順番に処理を行うように見えますが、実際にはそうはなりません。async/awaitによるサスペンドが全く効かず、暴走しているように感じますが、処理は速いです。フェイク非同期関数を実行する際、サスペンドなしで、イベントループにひたすら順番に投入していくイメージかと思います。

ちなみに、MDNのドキュメント（Array.prototype.forEach()）には、

```
Note: forEach expects a synchronous function.

forEach does not wait for promises. Make sure you are aware of the implications while using promises (or async functions) as forEach callback.
```

と書いてあるのですが、たしかに、その通りになりました。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

##その他
下記のサンプルコードは、Node.jsv14.15.5でテストしたものです。
また、私の環境（Ubuntu + Node.js）で実行したところ、処理時間は120ms~125ms程度でした。さらに、蛇足ですが、arrayのアイテム数を100=>10000（百倍）などにしても、処理時間はそれほど変わらず、120ms=>190msでした。

##サンプルコード

```JavaScript:
console.time();

let fakeSearchResult = [];

//forLoopを使って、整数1~100が入っているarrayを作ります
let arrayN = [];
const arrayLength = 100;
for (let i = 0; i < arrayLength; i++) {
    arrayN[i] = i + 1;
}

//最後にソートをかけて、元の順番に戻すための関数
function arraySorter(array){
    array.sort(function(a, b) {
        return a.index - b.index;
    });
}

//データベース検索を偽装したフェイク非同期関数
function fakeDatabaseQuery(item) {
    //ランダム関数で2桁の整数を作成します
    const elapse = Math.floor(Math.random() * 100);
    //プロミスオブジェクトをリターンします
    return new Promise(function(resolve, reject) {
        const obj = {index:item, elapse:elapse}
        //setTimeoutを使い、意図的に遅延を作りだしています
        setTimeout(() => resolve(obj), elapse);
    });
}

//forEachを使って、フェイク非同期関数をループ実行
function processArray(array) {
    array.forEach(async (item) => {
        //awaitはプロミスオブジェクトを待っていて、fulfilledになると、
        //プロミスオブジェクトから値を取り出します
        const obj = await fakeDatabaseQuery(item);
        fakeSearchResult.push(obj);
        if (fakeSearchResult.length === array.length){
            console.log('fakeSearchResult-1:',fakeSearchResult);//順番がぐだぐだです
            arraySorter(fakeSearchResult);//arrayにソートをかけて、当初の順番に戻します
            console.log('fakeSearchResult-2:',fakeSearchResult);//ソートして元の順番に戻りました
            console.timeEnd();
        }
    });
}

processArray(arrayN);

```

https://qiita.com/soichirowada/items/894be5a2532b477c4b29

https://qiita.com/soichirowada/items/77d358ea64f85d21667b

https://qiita.com/soichirowada/items/7f0ad6c55075252ee012

https://qiita.com/soichirowada/items/263ee198c3d793be6f59