JavaScript/TypeScript で Promise を直列実行できるが、結局どう実装すればよいのか？（可読性・実行速度）

# 目的

- こんな記事を書いた
    - [JavaScript/TypeScript で Promise を直列実行する](https://qiita.com/arx8/items/021e19189c9e0f60748e)
- で。たくさん書き方あるが、結局どれがいいのか？
- そうだ、パフォーマンス測定しよう

# tl:dr;

- 「可読性 > 実行速度」なら、「for-of」または「while」
    - https://github.com/arx-8/try-sequential-promises/blob/master/src/sequentialForOf.ts
    - https://github.com/arx-8/try-sequential-promises/blob/master/src/sequentialWhile.ts
- 「可読性 <<<<< 実行速度」なら、「async/await なしの reduce」
    - https://github.com/arx-8/try-sequential-promises/blob/master/src/sequentialReduceNoAsync.ts

# 計測環境

- OS: Windows 7
- CPU: Core i5 2500K (1.6 GHz でクロック固定)
- Node: v12.2.0
- TypeScript: 3.4.5
- benchmark: 2.1.4
    - https://www.npmjs.com/package/benchmark

## src

https://github.com/arx-8/try-sequential-promises

# 計測結果

## トランスパイル後のファイルサイズ

|                            | es5  | es2016 | es2017 |
|----------------------------|------|--------|--------|
| sequentialForOf.js         | 3446 | 1030   | 381    |
| sequentialReduce.js        | 4000 | 1300   | 611    |
| sequentialReduceNoAsync.js | 1038 | 988    | 988    |
| sequentialWhile.js         | 3401 | 1103   | 454    |

単位は byte

### es2016以下の場合

- async/await を使用した場合、トランスパイルで polyfill が挿入される
- よって、 async/await を使用しなければ、polyfill 分のサイズを削減できる
- ただし、その他の実装でも一切 async/await 使ってない場合に限る
    - src全体で見て、他の実装で async/await を使っている場合、その実装のために polyfill が挿入される
    - そのため、直列実行の実装を工夫しても polyfill 分のサイズは必ず増える

### es2017以上の場合

- いずれの実装も、polyfillは挿入されない
- よって、書いたコード分のファイルサイズになる

### 所感

- まぁ、妥当な結果

## ベンチマーク

- 各 target の出力結果の JavaScript を実行
- 1 スレッド、100 Promise の直列処理 * 5 回計測の平均
    - es5 だけ 1 回異常値が出たため除外、 4 回計測の平均としている
- 「Relative」列は、最速を 100 % とした相対値

### target: es5

| Case name               | Average (ops/sec) | Relative |
|-------------------------|-------------------|----------|
| sequentialReduce        | 181328.5          | 35.62%   |
| sequentialReduceNoAsync | 509098.0          | 100.00%  |
| sequentialForOf         | 316693.8          | 62.21%   |
| sequentialWhile         | 303165.3          | 59.55%   |

### target: es2016

| Case name               | Average (ops/sec) | Relative |
|-------------------------|-------------------|----------|
| sequentialReduce        | 124173.6          | 26.35%   |
| sequentialReduceNoAsync | 471195.6          | 100.00%  |
| sequentialForOf         | 120810.6          | 25.64%   |
| sequentialWhile         | 119936.8          | 25.45%   |

### target: es2017

| Case name               | Average (ops/sec) | Relative |
|-------------------------|-------------------|----------|
| sequentialReduce        | 387086.8          | 75.98%   |
| sequentialReduceNoAsync | 509441.2          | 100.00%  |
| sequentialForOf         | 466661.2          | 91.60%   |
| sequentialWhile         | 446598.4          | 87.66%   |

### 所感

- こうして見ると「reduce + async/await なし」のパターンが最速
- 「promise sequential」でググってよく出る reduce 使ったサンプルコードは、一応理にかなってたらしい

# まとめ

- 測定しておいてアレだが、ぶっちゃけ「非同期処理を直列に実行したいけど、プログラムの処理時間（マイクロ秒・ナノ秒単位）は切り詰めたい」なんて需要が思い付かない・・・
- なので、可読性の高い（※主観）「for-of」または「while」で実装すればよいのではないか
    - ループの途中で break したいケースでは、確実に「for-of」または「while」の方が実装しやすい
- もしパフォーマンスチューニングする場合は、async/await を捨て糞コードへ先祖返りする前に、見直すべき部分があるはず