Node.jsの非同期処理を複数回繰り返す

Node.js v10.11.0 を使用しています。

## 同期処理のループ
まずはただのループの例。1から5までの数字を足して出力します。
for文は嫌いですが、数字のシークエンスを用意してくれるAPIがネイティブにないのだから仕方ない。

```sync-loop.js
// 同期ループ
let sum = 0
for (let i = 1; i <= 5; i++) {
  sum += i
  console.log(`Add ${i}: ${sum}`)
}

// 同期ループ後の処理
console.log(`Result: ${sum}`)
```
```output.txt
Add 1: 1
Add 2: 3
Add 3: 6
Add 4: 10
Add 5: 15
Result: 15
```

## 非同期処理（setTimeout）のループ
setTimeoutで足し算を非同期に行います。
ループのイメージを掴む為、まずはベタ書きしてみます。

### 下書き
```settimeout-loop-draft.js
// 経過時間計測用
const beginDate = Date.now()
const showElasped = () => {
  console.log(`Elasped: ${Date.now() - beginDate}ms`)
}

// 非同期ループ
let sum = 0
let i = 1
setTimeout(() => {
  sum += i
  console.log(`Add ${i}: ${sum}`)
  showElasped()
  i++
  setTimeout(() => {
    sum += i
    console.log(`Add ${i}: ${sum}`)
    showElasped()
    i++
    setTimeout(() => {
      sum += i
      console.log(`Add ${i}: ${sum}`)
      showElasped()
      i++
      setTimeout(() => {
        sum += i
        console.log(`Add ${i}: ${sum}`)
        showElasped()
        endCompute()
      }, i * 500)
    }, i * 500)
  }, i * 500)
}, i * 500)

// 非同期ループ終了時の着地先
const endCompute = () => {
  console.log(`Result: ${sum}`)
  showElasped()
}
```
```output.txt
Add 1: 1
Elasped: 513ms
Add 2: 3
Elasped: 1517ms
Add 3: 6
Elasped: 3024ms
Add 4: 10
Elasped: 5024ms
Result: 10
Elasped: 5025ms
```

お手本のようなコールバック地獄ですね。
4まで足したところで満足しました。

さて、この繰り返しをfor文で再現するのは難しいのではないでしょうか。少なくとも自分は思いつきません。
というかどうみても再帰の形をしているように思います。
ということで再帰で実装してみましょう。

### 清書
```settimeout-loop.js
// 経過時間計測用
const beginDate = Date.now()
const showElasped = () => {
  console.log(`Elasped: ${Date.now() - beginDate}ms`)
}

// 非同期ループ
let sum = 0
const setAddProcess = i => {
  if (i > 5) {
    endCompute()
    return
  }
  setTimeout(() => {
    sum += i
    console.log(`Add ${i}: ${sum}`)
    showElasped()
    setAddProcess(i + 1)
  }, i * 500)
}
setAddProcess(1)

// 非同期ループ終了時の着地先
const endCompute = () => {
  console.log(`Result: ${sum}`)
  showElasped()
}
```
```output.txt
Add 1: 1
Elasped: 511ms
Add 2: 3
Elasped: 1516ms
Add 3: 6
Elasped: 3017ms
Add 4: 10
Elasped: 5022ms
Add 5: 15
Elasped: 7524ms
Result: 15
Elasped: 7525ms
```

`i`は処理から処理へ受け渡していますが、`sum`はトップレベルに置きっ放しにしてみました。
どちらの変数もどちらの形でも扱えると思います。

## 非同期処理（Promise）のループ

ここでせっかくなのでPromiseを使ってみます。

### 下書き

```promise-loop-draft.js
// 経過時間計測用
const beginDate = Date.now()
const showElasped = () => {
  console.log(`Elasped: ${Date.now() - beginDate}ms`)
}

// 非同期ループ
let sum = 0
Promise.resolve(1)
  .then(i => {
    return new Promise(resolve => {
      setTimeout(() => {
        sum += i
        console.log(`Add ${i}: ${sum}`)
        showElasped()
        resolve(i + 1)
      }, i * 500)
    })
  })
  .then(i => {
    return new Promise(resolve => {
      setTimeout(() => {
        sum += i
        console.log(`Add ${i}: ${sum}`)
        showElasped()
        resolve(i + 1)
      }, i * 500)
    })
  })
  .then(i => {
    endCompute()
  })

// 非同期ループ終了時の着地先
const endCompute = () => {
  console.log(`Result: ${sum}`)
  showElasped()
}
```
```output.txt
Add 1: 1
Elasped: 514ms
Add 2: 3
Elasped: 1520ms
Result: 3
Elasped: 1521ms
```

2まで足したところで満足しました。
怠惰はプログラマーの美徳です。

コールバック地獄がメソッドチェーン地獄になり下がりました。
この繰り返しは簡単ですね。

```js
Promise.resolve()
  .then(() => console.log('foo'))
  .then(() => console.log('bar'))
```

はこのように書き換えられます。

```js
let promise = Promise.resolve()
promise = promise.then(() => console.log('foo'))
promise = promise.then(() => console.log('bar'))
```

### 清書
```promise-loop.js
// 経過時間計測用
const beginDate = Date.now()
const showElasped = () => {
  console.log(`Elasped: ${Date.now() - beginDate}ms`)
}

// 非同期ループ
let sum = 0
let addPromise = Promise.resolve(1)
for (let times = 1; times <= 5; times++) {
  addPromise = addPromise.then(i => {
    return new Promise(resolve => {
      setTimeout(() => {
        sum += i
        console.log(`Add ${i}: ${sum}`)
        showElasped()
        resolve(i + 1)
      }, i * 500)
    })
  })
}
addPromise = addPromise.then(i => {
  endCompute()
})

// 非同期ループ終了時の着地先
const endCompute = () => {
  console.log(`Result: ${sum}`)
  showElasped()
}
```
```output.txt
Add 1: 1
Elasped: 510ms
Add 2: 3
Elasped: 1517ms
Add 3: 6
Elasped: 3023ms
Add 4: 10
Elasped: 5024ms
Add 5: 15
Elasped: 7526ms
Result: 15
Elasped: 7526ms
```

## 非同期処理（Promise）の動的ループ

さて、しれっと誤魔化していましたが、実は上記のPromiseを使う前と後の例では、やってることが微妙に違います。

Promiseを使う前は、2を足す処理が終わる頃に3を足す処理を、3を足す処理が終わる頃に4を足す処理を追加していました。
Promiseを使う例では、はじめに1〜5を足す処理を全て繋げた形で一気に追加しています。

このやり方は、今回のようにループする回数が固定な場合は使えますが、非同期処理を実行してみないとループ回数が分からないケースでは使えません（例えば、10を超えたら足すのをやめる、等）。

Promiseを使ったまま以前の柔軟性を維持するには、やはり再帰しかないんじゃないでしょうか。
具体的には、非同期処理が終わる頃に自身が属すPromiseチェーンに次のthenを足していくやり方です。

というわけで、「10を超えたら足すのをやめる」ループをPromiseと再帰処理でやってみます。

```promise-loop-dynamic.js
// 経過時間計測用
const beginDate = Date.now()
const showElasped = () => {
  console.log(`Elasped: ${Date.now() - beginDate}ms`)
}

// 非同期ループ
let sum = 0
let promise = Promise.resolve(1)
const addAddPromise = () => {
  promise = promise.then(i => {
    return new Promise(resolve => {
      setTimeout(() => {
        sum += i
        console.log(`Add ${i}: ${sum}`)
        showElasped()
        if (sum <= 10) {
          addAddPromise()
        } else {
          addEndPromise()
        }
        resolve(i + 1)
      }, i * 500)
    })
  })
}
const addEndPromise = () => {
  promise = promise.then(i => {
    endCompute()
  })
}
addAddPromise()

// 非同期ループ終了時の着地先
const endCompute = () => {
  console.log(`Result: ${sum}`)
  showElasped()
}
```
```output.txt
Add 1: 1
Elasped: 510ms
Add 2: 3
Elasped: 1518ms
Add 3: 6
Elasped: 3020ms
Add 4: 10
Elasped: 5026ms
Add 5: 15
Elasped: 7526ms
Result: 15
Elasped: 7527ms
```

こうなるとPromiseのメリットは特に無い気がします。
素直に上記のPromiseを使わない書き方をした方が良いんじゃないでしょうか。

## 結論
なんかライブラリとか使った方が良さそうですね。