async/awaitを、Array.prototype.forEachで使う際の注意点、という話

#はじめに、の前に（2020/10/29 追記）

（いつの間にか）MDNに、forEachはPromiseを待たない、という趣旨の説明が書かれています。

MDNを読めば、この記事の内容を読む必要は無いと思います……。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

#はじめに
Javascriptでは、async/awaitを使う事によって、非同期処理が非常に簡潔に書ける様になっています。
今後はasync/awaitを使ったプログラミングが増えていくと思います。

node.jsで、async/awaitを使ったプログラミングをしていたのですが、ハマった事があったので、自分用の備忘録としてメモ書きです。

#実行環境
OS: Debian 9.6
node.js: v11.2.0

#問題となったコード
配列をconsole.logで出力するだけのプログラムです。

ただし、console.log(value)の前に、sleep(1000)が入っていて、1秒間sleepする様にしています。
このプログラムは、数値が1秒毎に出力され、5秒掛けて全てが出力される事を、意図していました。

```javascript:sample
'use strict';

function sleep(time) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve();
        }, time);
    });
}

(async () => {
    const a = [1,2,3,4,5];
    a.forEach(async (value) => { //　→ await a.forEach(... と書いても結果は同じです。
        await sleep(1000);
        console.log(value)
    });
})();
```

出力結果は、下記の通りになります。
この場合、1秒経過後、一瞬で全ての数字が出力されます。

```text:output
# 下記の数列は、一瞬で表示されます
1
2
3
4
5
```
意図していたのは、5秒掛けて1つずつ数字が出力される事でしたので、このままだと問題があります。



#forEach - Polyfill
MDNウェブドキュメントで、forEachのPolyfillを調べてみました。
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach#Polyfill

下記がそのコードです。（コメントやコードを、一部省略しています）

```javascript:forEach
if (!Array.prototype.forEach) {

  Array.prototype.forEach = function(callback/*, thisArg*/) {
    // 一部省略

    while (k < len) {
      var kValue;
      if (k in O) {
        kValue = O[k];
        callback.call(T, kValue, k, O); // ←awaitが付いていない
      }

      k++;
    }
  };
}
```

重要なのは、

- Array.prototype.forEachは、async functionでは無い。
- callback.callにawaitが付いていない。

という点です。
その為、callback.callで処理が一時停止する事はありません。

#sample修正版
その為、sampleのコードは、下記の様に書く必要があります。
下記の様に書くと、1秒ごとに数字が出力されました。

```javascript:sample修正版
'use strict';

function sleep(time) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve();
        }, time);
    });
}

(async () => {
    const a = [1,2,3,4,5];
    for(let i of a) {
        await sleep(1000);
        console.log(i);
    }
})();
```

この様な場合は、forEachなんて使わずに、for-of文を使いなさい、と言う事が解りました。

~~自分でasyc/awaitに対応したforEachを作るのも良いかも知れません。~~

今なら、[array-foreach-async](https://www.npmjs.com/package/array-foreach-async)というライブラリがあるとの情報を、コメントにてご指摘を頂きました。


### 補足

以前は、修正例を下記コードで紹介していました。このコードでは、for-of文を使わず、for文を使っています。
ですが、for-of文を使う方が良いかと思います。

```javascript:sample修正版-修正前
'use strict';

function sleep(time) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve();
        }, time);
    });
}

(async () => {
    const a = [1,2,3,4,5];
    for(let i = 0; i < a.length; i++) {
        await sleep(1000);
        console.log(a[i]);
    }
})();
```

# 余談 - Promise.all

1回づつ処理を待たずに、全て並列に動かしたい場合は、Promise.allを使用すると良いと思います。
Promise.allについては、コメントにてご指摘を頂きました。

- [async-awaitでもforEachしたい！](https://qiita.com/jkr_2255/items/62b3ee3361315d55078a)

一応、私も適当にサンプルを書いてみました。
解りづらければごめんなさい。

（補足1は、Windows環境、nodejs version: 11.3.0で確認しています。）

```javascript:補足1
'use strict';

// Function - sleep_and_print
function sleep_and_print(max_time, text) {
    // max_timeを上限に、ランダムな時間だけsleepする
    // sleep後、Textとsleep時間を表示する。
    
    return new Promise((resolve, reject) => {
        const wait = Math.random() * max_time

        setTimeout(() => {
            console.log("Text: " + text + ", Wait: " + wait / 1000.0);
            resolve();
        }, wait);
    });
}

// Main
(async () => {
    //----------------------------------------------------
    // 処理1 - Promise.all
    //----------------------------------------------------
    console.log("----- Promise.all -----");
    let start = Date.now(); // 処理時間の測定用

    // 表示させるテキスト
    let a = ["A","B","C","D","E"];

    // Promiseの配列を生成
    const b = a.map(item => sleep_and_print(5000, item));

    // 配列bに入っている処理がすべて終わるまで、await
    // 並列処理を行う。
    await Promise.all(b); 

    // 処理は5秒以内に終わる、はず
    let stop = Date.now(); // 処理時間の測定用
    let result = stop - start;
    console.log("Time: " + result / 1000.0); // 処理時間表示


    //----------------------------------------------------
    // 処理2 - for-of
    //----------------------------------------------------
    console.log("----- for-of -----");
    start = Date.now(); // 処理時間の測定用

    // 表示させるテキスト
    a = ["A","B","C","D","E"];

    for(let i of a) {
        await sleep_and_print(5000, i);
    }

    // 処理は5秒以上かかる、はず
    stop = Date.now(); // 処理時間の測定用
    result = stop - start;
    console.log("Time: " + result / 1000.0); // 処理時間表示
})();
```

下記が実行結果です。
Waitと書かれた部分が、その処理で行われたsleepの時間、最後のTimeが全ての処理に掛かった時間です。

Promise.allだと、並列実行のため、最悪でも5sec+数msec以内に処理が終了します。
また、並列実行のため、処理が終了する順序はランダムです。

for-ofだと、全ての処理を逐次実行するため、平均で12.5sec程掛かります。
処理は、順序通りに実行されます。

```text:補足1-実行結果
----- Promise.all -----
Text: B, Wait: 3.140756979867685
Text: C, Wait: 3.8320302876606918
Text: A, Wait: 4.607162449097081
Text: E, Wait: 4.875497964313306
Text: D, Wait: 4.996697681841334
Time: 5.003
----- for-of -----
Text: A, Wait: 4.342562029541116
Text: B, Wait: 4.270371805765942
Text: C, Wait: 3.542954967565629
Text: D, Wait: 4.503098764260032
Text: E, Wait: 1.1583077650003248
Time: 17.84
```

#最後に
async/awaitに対応したforEachを探してみたのですが、私が調べた限りでは見つかりませんでした。
標準のjavascriptには無いのでしょうか。
ただし、ライブラリは存在するとの事なので、それを使ってみるのが良いかもしれません。

コメントで皆様に色々と情報を頂きましたので、そちらも参照してみて下さい。

**頂いたコメントの内容の一部を、記事にフィードバックさせて頂きました。(2018/12/06)**
情報を頂いた皆様に感謝申し上げます。本当にありがとうございました。


#参考リンク
MDNウェブドキュメント - forEach Polyfill
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach#Polyfill

JavaScript: async/await with forEach()
https://codeburst.io/javascript-async-await-with-foreach-b6ba62bbf404