async/awaitの並列実行/直列実行/即時実行の書き方(ECMAScript 2017)【まとめ】(for-of,forEach,map,reduce,Pormise.all)

# はじめに

async/awaitで**①並列実行**, **②直列実行**, **③即時実行(待受無し)**の書き方をまとめました。(**ECMAScript 2017**前提)

記事等では、**(A) for-of(iterableのループ)**を使う方法と、**(B) Array.prototype(配列のメソッド)**を使う方法に2分されますが、**①並列実行**, **②直列実行**, **③即時実行(待受無し)**で、(A)と(B)のどちらを使うべきか(どちらが可読性が高いか)を考えます。

# (1) 複数文の繰り返し処理

## (1-A) for-of(iterableのループ)

### (1-A-①) 並列実行
``` javascript
const secSleep = sec => new Promise(resolve => setTimeout(resolve, sec * 1000));
(async () => {
  console.log('START');
  let secSleeps = [];
  for ( let v of [3, 2, 1] ) {
    secSleeps.push( (async () => {
      console.log(`  START sleep ${v}s`);
      await secSleep(v);
      console.log(`  END sleep ${v}s`);
    })());
  }
  await Promise.all(secSleeps);
  console.log('END');
})();
// =>
// START
//   START sleep 3s
//   START sleep 2s
//   START sleep 1s
//   END sleep 1s
//   END sleep 2s
//   END sleep 3s
// END
```

### (1-A-②) 直列実行
``` javascript
const secSleep = sec => new Promise(resolve => setTimeout(resolve, sec * 1000));
(async () => {
  console.log('START');
  for ( v of [3, 2, 1] ) {
    console.log(`  START sleep ${v}s`);
    await secSleep(v);
    console.log(`  END sleep ${v}s`);
  }
  console.log('END');
})();
// =>
// START
//   START sleep 3s
//   END sleep 3s
//   START sleep 2s
//   END sleep 2s
//   START sleep 1s
//   END sleep 1s
// END
```

### (1-A-③) 即時実行(待受無し)
``` javascript
const secSleep = sec => new Promise(resolve => setTimeout(resolve, sec * 1000));
(async () => {
  console.log('START');
  for ( let v of [3, 2, 1] ) {
    (async() => {
      console.log(`  START sleep ${v}s`);
      await secSleep(v);
      console.log(`  END sleep ${v}s`);
    })();
  }
  console.log('END');
})();
// =>
// START
//   START sleep 3s
//   START sleep 2s
//   START sleep 1s
// END
//   END sleep 1s
//   END sleep 2s
//   END sleep 3s
```

## (1-B) Array.prototype(配列のメソッド)

### (1-B-①) 並列実行
``` javascript
const secSleep = sec => new Promise(resolve => setTimeout(resolve, sec * 1000));
(async () => {
  console.log('START');
  await Promise.all([3, 2 ,1].map( async v => {
    console.log(`  START sleep ${v}s`);
    await secSleep(v);
    console.log(`  END sleep ${v}s`);
  }));
  console.log('END');
})();
// =>
// START
//   START sleep 3s
//   START sleep 2s
//   START sleep 1s
//   END sleep 1s
//   END sleep 2s
//   END sleep 3s
// END
```

### (1-B-②) 直列実行
``` javascript
const secSleep = sec => new Promise(resolve => setTimeout(resolve, sec * 1000));
(async () => {
  console.log('START');
  await [3, 2 ,1].reduce( (promise, v) => {
    return promise.then( async () => {
      console.log(`  START sleep ${v}s`);
      await secSleep(v);
      console.log(`  END sleep ${v}s`);
    });
  }, Promise.resolve());
  console.log('END');
})();
// =>
// START
//   START sleep 3s
//   END sleep 3s
//   START sleep 2s
//   END sleep 2s
//   START sleep 1s
//   END sleep 1s
// END
```

### (1-B-③) 即時実行(待受無し)

``` javascript
const secSleep = sec => new Promise(resolve => setTimeout(resolve, sec * 1000));
(async () => {
  console.log('START');
  [3, 2 ,1].forEach( async v => {
    console.log(`  START sleep ${v}s`);
    await secSleep(v);
    console.log(`  END sleep ${v}s`);
  });
  console.log('END');
})();
// =>
// START
//   START sleep 3s
//   START sleep 2s
//   START sleep 1s
// END
//   END sleep 1s
//   END sleep 2s
//   END sleep 3s
```


# (2) 単独式の繰り返し処理

## (2-A) for-of(iterableのループ)

### (2-A-①) 並列実行
``` javascript
const secSleep = sec => new Promise(resolve => setTimeout(resolve, sec * 1000));
const secSleepWithLog = async sec => {
  console.log(`  START sleep ${sec}s`);
  await secSleep(sec);
  console.log(`  END sleep ${sec}s`);
}
(async () => {
  console.log('START');
  let secSleeps = [];
  for ( let v of [3, 2, 1] ) {
    secSleeps.push(secSleepWithLog(v));
  }
  await Promise.all(secSleeps);
  console.log('END');
})();
// =>
// START
//   START sleep 3s
//   START sleep 2s
//   START sleep 1s
//   END sleep 1s
//   END sleep 2s
//   END sleep 3s
// END
```

### (2-A-②) 直列実行
``` javascript
const secSleep = sec => new Promise(resolve => setTimeout(resolve, sec * 1000));
const secSleepWithLog = async sec => {
  console.log(`  START sleep ${sec}s`);
  await secSleep(sec);
  console.log(`  END sleep ${sec}s`);
}
(async () => {
  console.log('START');
  for ( v of [3, 2, 1] ) {
    await secSleepWithLog(v);
  }
  console.log('END');
})();
// =>
// START
//   START sleep 3s
//   END sleep 3s
//   START sleep 2s
//   END sleep 2s
//   START sleep 1s
//   END sleep 1s
// END
```

### (2-A-③) 即時実行(待受無し)
``` javascript
const secSleep = sec => new Promise(resolve => setTimeout(resolve, sec * 1000));
const secSleepWithLog = async sec => {
  console.log(`  START sleep ${sec}s`);
  await secSleep(sec);
  console.log(`  END sleep ${sec}s`);
}
(async () => {
  console.log('START');
  for ( let v of [3, 2, 1] ) {
    secSleepWithLog(v);
  }
  console.log('END');
})();
// =>
// START
//   START sleep 3s
//   START sleep 2s
//   START sleep 1s
// END
//   END sleep 1s
//   END sleep 2s
//   END sleep 3s
```

## (2-B) Array.prototype(配列のメソッド)

### (2-B-①) 並列実行
``` javascript
const secSleep = sec => new Promise(resolve => setTimeout(resolve, sec * 1000));
const secSleepWithLog = async sec => {
  console.log(`  START sleep ${sec}s`);
  await secSleep(sec);
  console.log(`  END sleep ${sec}s`);
}
(async () => {
  console.log('START');
  await Promise.all([3, 2 ,1].map(secSleepWithLog));
  console.log('END');
})();
// =>
// START
//   START sleep 3s
//   START sleep 2s
//   START sleep 1s
//   END sleep 1s
//   END sleep 2s
//   END sleep 3s
// END
```

### (2-B-②) 直列実行
``` javascript
const secSleep = sec => new Promise(resolve => setTimeout(resolve, sec * 1000));
const secSleepWithLog = async sec => {
  console.log(`  START sleep ${sec}s`);
  await secSleep(sec);
  console.log(`  END sleep ${sec}s`);
}
(async () => {
  console.log('START');
  await [3, 2 ,1].reduce( (promise, v) => {
    return promise.then( async () => secSleepWithLog(v) );
  }, Promise.resolve());
  console.log('END');
})();
// =>
// START
//   START sleep 3s
//   END sleep 3s
//   START sleep 2s
//   END sleep 2s
//   START sleep 1s
//   END sleep 1s
// END
```

### (2-B-③) 即時実行(待受無し)

``` javascript
const secSleep = sec => new Promise(resolve => setTimeout(resolve, sec * 1000));
const secSleepWithLog = async sec => {
  console.log(`  START sleep ${sec}s`);
  await secSleep(sec);
  console.log(`  END sleep ${sec}s`);
}
(async () => {
  console.log('START');
  [3, 2 ,1].forEach(secSleepWithLog);
  console.log('END');
})();
// =>
// START
//   START sleep 3s
//   START sleep 2s
//   START sleep 1s
// END
//   END sleep 1s
//   END sleep 2s
//   END sleep 3s
```

# 結果の比較

評価は**完全な主観**になりますが、不必要な無名関数呼び出しや、記載の長さを判断基準としてます。

| 処理内容 | (A) for-of<br>(iterableのループ) | (B) Array.prototype<br>(配列のメソッド) |
|:-:|:-:|:-:|
|1-① 複数文の並列実行| ★☆☆ | ★★★ |
|1-② 複数文の直列実行| ★★★ | ★☆☆ |
|1-③ 複数文の即時実行(待受無し)| ★☆☆ | ★★★ |
|2-① 単独式の並列実行| ★☆☆ | ★★★ |
|2-② 単独式の直列実行| ★★★ | ★☆☆ |
|2-③ 単独式の即時実行(待受無し)| ★★☆ | ★★★ |

思ったことを何点か

- 並列実行は**Array.prototype.map**を使う方がよい
- 直列実行は**for-of**を使う方がよい
- for-ofで記載を統一(即時実行等を行う)場合は、**非同期処理単位で関数化**しておけば即時関数が減り、可読性が上がる
- **Array.prototype.reduce**を使う記載は、今回のケースではなく、**単独式に引数がなければ**もう少し簡略化して書けた。

また、今回のケースでは、以下の2点注意事項があると思っている。

 - 実行結果の**戻り値を使わないケース**のため、戻り値を使用する場合はもう少し複雑になる。
 - **for-of**と**Array.prototype**では、for-of側はiterableであれば使用できるが、**Array.prototype**はArrayのみ。

# オレオレPromiseHandler作成

今回のケースでは、`await Promise.all([3, 2 ,1].map(secSleepWithLog));`の記載が個人的には書きやすい点と、可読性を考えて並列実行はArray.prototypeを使用し、直列実行はfor-ofを用いる場合、並列実行⇔直列実行の変更が難しいと感じた。

また、**iterable**が引数で、**直列実行の戻り値も利用でき**、かつ**並列/直列/即時を切替え可能(同じインターフェース)**の関数郡があるとよいのでは？と考え、下記の関数を定義してみる。

#### PromiseHandler.parallel(並列実行)

> PromiseHandler.parallel(*iterable*, async function callback(*currentValue*) {})

※ 返値のPromiseは、引数として渡されたiterableのすべての値(Promiseではない値も)を含んだ配列でresolveされます。

#### PromiseHandler.serial(直列実行)

> PromiseHandler.serial(*iterable*, async function callback(*currentValue*) {})

※ 返値のPromiseは、引数として渡されたiterableのすべての値(Promiseではない値も)を含んだ配列でresolveされます。

#### PromiseHandler.immediate(即時実行(待受無し))

> PromiseHandler.immediate(*iterable*, async function callback(*currentValue*) {})

※ 返値のPromiseは、undefinedでresolveされます。

### PromiseHandleの実装(コード)
``` javascript
const PromiseHandler = {
  parallel: async (iterable, callback) => {
    let array = [];
    for ( let v of iterable ) {
      array.push(callback(v));
    }
    return await Promise.all(array);
  },
  serial: async (iterable, callback) => {
    let array = [];
    for ( let v of iterable ) {
      array.push(await callback(v));
    }
    return array;
  },
  immediate: async (iterable, callback) => {
    for ( let v of iterable ) {
      callback(v);
    }
  },
}
```

## (2-C) オレオレPromiseHandler

作成したオブジェクトを使用して、**(2) 単独式の繰り返し処理**の、**①並列実行**, **②直列実行**, **③即時実行(待受無し)**を書いてみる。(**(1) 複数文の繰り返し処理は省略**)
なお、戻り値の制御も確認するために、戻り値の確認も入れている。

### (2-C-①) 並列実行
``` javascript
// PromiseHandlerが作成してある前提
const secSleep = sec => new Promise(resolve => setTimeout(resolve, sec * 1000));
const secSleepWithLog = async sec => {
  console.log(`  START sleep ${sec}s`);
  await secSleep(sec);
  console.log(`  END sleep ${sec}s`);
  return `"ret=${sec}"`; //戻り値確認用に追加
}
(async () => {
  console.log('START');
  let ret = await PromiseHandler.parallel([3, 2 ,1], secSleepWithLog);
  console.log(`END ${ret.join(",")}`);
})();
// =>
// START
//   START sleep 3s
//   START sleep 2s
//   START sleep 1s
//   END sleep 1s
//   END sleep 2s
//   END sleep 3s
// END "ret=3","ret=2","ret=1"
```

### (2-C-②) 直列実行
``` javascript
// PromiseHandlerが作成してある前提
const secSleep = sec => new Promise(resolve => setTimeout(resolve, sec * 1000));
const secSleepWithLog = async sec => {
  console.log(`  START sleep ${sec}s`);
  await secSleep(sec);
  console.log(`  END sleep ${sec}s`);
  return `"ret=${sec}"`; //戻り値確認用に追加
}
(async () => {
  console.log('START');
  let ret = await PromiseHandler.serial([3, 2 ,1], secSleepWithLog);
  console.log(`END ${ret.join(",")}`);
})();
// =>
// START
//   START sleep 3s
//   END sleep 3s
//   START sleep 2s
//   END sleep 2s
//   START sleep 1s
//   END sleep 1s
// END "ret=3","ret=2","ret=1"
```

### (2-C-③) 即時実行(待受無し)

``` javascript
// PromiseHandlerが作成してある前提
const secSleep = sec => new Promise(resolve => setTimeout(resolve, sec * 1000));
const secSleepWithLog = async sec => {
  console.log(`  START sleep ${sec}s`);
  await secSleep(sec);
  console.log(`  END sleep ${sec}s`);
  return `"ret=${sec}"`; //戻り値確認用に追加
}
(async () => {
  console.log('START');
  let ret = await PromiseHandler.immediate([3, 2 ,1], secSleepWithLog);
  console.log(`END ${ret}`);
})();
// =>
// START
//   START sleep 3s
//   START sleep 2s
//   START sleep 1s
// END undefined
//   END sleep 1s
//   END sleep 2s
//   END sleep 3s
```

## オレオレPromiseHandlerの評価

繰り返しますが、**評価は主観です**

| 処理内容 | (A) for-of<br>(iterableのループ) | (B) Array.prototype<br>(配列のメソッド) | (C) オレオレPromiseHandler |
|:-:|:-:|:-:|:-:|
|2-① 単独式の並列実行| ★☆☆ | ★★★ | ★★★★★ |
|2-② 単独式の直列実行| ★★★ | ★☆☆ | ★★★★★ |
|2-③ 単独式の即時実行(待受無し)| ★★☆ | ★★★ | ★★★★★ |

**①並列実行**, **②直列実行**, **③即時実行(待受無し)**について、~~実際のケースではserial以外はそんなにメリットが多くない気がするが~~すべて**使用するプロパティ名を変えるだけで切り替え可能**で、**記法もシンプル**になった！

## (参考) **generator**でserial処理が可能なことの確認
``` javascript
// PromiseHandlerが作成してある前提
const secSleep = sec => new Promise(resolve => setTimeout(resolve, sec * 1000));
const secSleepWithLog = async sec => {
  console.log(`  START sleep ${sec}s`);
  await secSleep(sec);
  console.log(`  END sleep ${sec}s`);
  return `"ret=${sec}"`; //戻り値確認用に追加
}
const increseGenerator = function*() {
  let v = 1;
  while(true) {
    yield v;
    v = v * 2;
  }
}
(async () => {
  console.log('START');
  let ret = await PromiseHandler.serial(increseGenerator(), secSleepWithLog);
  console.log(`END ${ret.join(",")}`);
})();
// =>
// START
//   START sleep 1s
//   END sleep 1s
//   START sleep 2s
//   END sleep 2s
//   START sleep 4s
//   END sleep 4s
//   START sleep 8s
//   END sleep 8s
//   START sleep 16s
//   END sleep 16s
//   START sleep 32s
// ...無限に続く
```

# さいごに

async/await記法はネストが浅くかけて可読性も高いと思っているが、for-ofで小括弧をつけ忘れたり、今回のように順番に丁寧に書いても、たまにアレってなるので、個人用メモを兼ねている。


# 参考記事(Qiita)

## for-of, Promise.all, reduceを使用した記事
[async/awaitでiterateがんばる](https://qiita.com/nazomikan/items/341b1b6e196c7150bfa6)

## for-ofとPromise.allを使用した記事
[async/awaitを、Array.prototype.forEachで使う際の注意点、という話](https://qiita.com/frameair/items/e7645066075666a13063)

## Promise.allを使用した記事

### map

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

### for-ofによるpush

[async/await で複数の非同期処理を待つときに注意したいこと](https://qiita.com/im36-123/items/c0678a46ee0f8e44e150)

## reduceを使用した記事

[JavaScript/TypeScript で Promise を直列実行する](https://qiita.com/arx8/items/021e19189c9e0f60748e)


## 関数定義

### all時の関数呼び出しをラップ(()を省略)
[async-await内で並列処理](https://qiita.com/m0a/items/a83a08b830c518cb85c7)
※ TypeScript

## モジュールの紹介
[async/awaitやPromiseで簡単に配列のイテレーションできるようにする](https://qiita.com/toniov/items/127267fb64a960e8166e)