node.jsでHIITのTimerを作った


フィヨルドブートキャンプのプラクティスでnpmを自作しました。
せっかくなのでqiitaで公開させてもらおうと思ったので、勇気を出して書かせてもらってます。

「もっとこうした方がいいんじゃない？」などのご意見がありましたらコメントしてもらえると嬉しいです！

## どんなnpmを作ったのか
作ったnpmは、最近流行りのトレーニング手法である**HIIT（High Intensity Interval Training）のタイマー**です。

> [話題の筋トレ「HIIT」とは？](https://www.s-re.jp/magazine/health/102/)
HIITとは、「High Intensity Interval Training（高強度インターバルトレーニング）」の略で、負荷の高い運動と小休憩を繰り返すトレーニング法のこと。限界まで体を追い込むことで、常に脂肪が燃焼しやすい状態をキープし、体脂肪減少と筋肉増量効果を得るものです。

**実際に作成したnpmはこちらで確認できます↓**
- npm：[hiit_timer](https://www.npmjs.com/package/hiit_timer)
- GitHub：github.com/R-Tsukada/hiit-timer

### 実際どんな感じで動くのか

1. ワークアウト時間の選択

   [![Image from Gyazo](https://i.gyazo.com/4f13b1fd82af8466a31b068fa6928a89.gif)](https://gyazo.com/4f13b1fd82af8466a31b068fa6928a89)


2. インターバル時間の選択

   [![Image from Gyazo](https://i.gyazo.com/7d746f683e9d1e7b3c032c7535e7fd50.gif)](https://gyazo.com/7d746f683e9d1e7b3c032c7535e7fd50)


3. 1,2を何セット行うかを選択

   [![Image from Gyazo](https://i.gyazo.com/c3bb8633b6cc34eebb9f2b09f5131dd7.gif)](https://gyazo.com/c3bb8633b6cc34eebb9f2b09f5131dd7)

## 作成したコード

### 最終的なコード
```
const log = require('single-line-log').stdout
const player = require('play-sound')(opts = {})
const { prompt } = require('enquirer')
const emoji = require('node-emoji');

async function hiitTimer(){
  const workoutTimer = await prompt(
    [
      {
        type: 'select',
        name: 'workoutTime',
        message: 'select workout time',
        choices: ['10', '20', '30', '40', '50', '60']
      },
      {
        type: 'select',
        name: 'restTime',
        message: 'select rest time',
        choices: ['10', '20', '30', '40', '50', '60']
      },
      {
        type: 'select',
        name: 'set',
        message: 'select set number?',
        choices: ['1', '2', '3', '4', '5', '7', '8', '9', '10']
      }
    ]
  )
  const workout = workoutTimer.workoutTime
  const rest = workoutTimer.restTime
  const set = workoutTimer.set

  running(set, workout, rest)
}

async function running(setNumber, workout, rest){
  await log('\x1b[32m10 seconds before Workout')
  await firstWorkout(10)
  for (let i = 0; i < setNumber; i++) {
    log(emoji.get('fist') + ` Set${i + 1} Workoout Start ` + emoji.get('fist'))
    await intervalTimer(workout)
    log((emoji.get('relaxed')) + ` Set${i + 1} Break ` + (emoji.get('relaxed')))
    await intervalTimer(rest)
  }
  await log((emoji.get('congratulations')) + ' Workout Finish' + (emoji.get('exclamation')) + 'Good Job' + (emoji.get('exclamation')) + (emoji.get('congratulations')))
}

function intervalTimer(selectWorkout) {
  return new Promise(resolve => {
      setTimeout(() => {
        const startEffect = player.play('start_effect.mp3')
        resolve()
      }, selectWorkout * 1000)
    }
  )
}

function firstWorkout(second) {
  return new Promise(resolve => {
    setTimeout(() => {
      const startEffect = player.play('start_effect.mp3')
      resolve()
    }, second * 1000)
  })
}

hiitTimer()
```

### 悩んだ点
今回色々悩んだことが、「ワークアウトとインターバルを設定したセット数を繰り返す」ことでした。

繰り返すということは`setInterval`を使えばいいのでは？と考えて、↓のような考え方をしていました。

```
console.log('First Workuout Start')
const running = (function(){
  console.log('First Workuout Start')
  let number = 1
  setInterval(function() {
    if (number === 2) {
      console.log('break')
    } else if (number === 3) {
      console.log('Workuout Start')
    }
    number += 1
  }, 1000)
})

async function play(hiitNumber){
  for (let i = 0; i < hiitNumber; i++) {
    running()
}}
play(1)
```

このコードでも一応実装したい通りの動きになったのですが、、

```
First Workuout Start
break
Workuout Start
```

`play(3)`のようにセット数を複数回繰り返すと、

```
First Workuout Start
break
break
break
Workuout Start
Workuout Start
Workuout Start
```

`break`、`Workuout Start`が連続で処理されてしまってインターバルのタイマーになりませんでした。

非同期的に処理されてしまってるので、同期処理になるようにコードを変える必要性があるなと思いました。

### 修正した点
非同期処理が原因でインターバルタイマーの動きになってないので、同期処理になるようにコードを修正。

そもそも`setInterval`が非同期処理を前提にしたメソッドなので、`setInterval`ではなく、`setTimeout`を繰り返し処理を使うことにしました。

`intervalTimer`というメソッドを作成して、`workout`（運動時間）、`rest`（休憩時間）の時間をそれぞれ引数で渡します。

まずは`enquire`を使って`workout`, `rest`, `set`（セット数）を決めます。

```
sync function hiitTimer(){

  /* enquireで,workout、rest、setを選択 */
  const workoutTimer = await prompt(
    [
      {
        type: 'select',
        name: 'workoutTime',
        message: 'select workout time',
        choices: ['10', '20', '30', '40', '50', '60']
      },
      {
        type: 'select',
        name: 'restTime',
        message: 'select rest time',
        choices: ['10', '20', '30', '40', '50', '60']
      },
      {
        type: 'select',
        name: 'set',
        message: 'select set number?',
        choices: ['1', '2', '3', '4', '5', '7', '8', '9', '10']
      }
    ]
  )
  const workout = workoutTimer.workoutTime
  const rest = workoutTimer.restTime
  const set = workoutTimer.set

  running(set, workout, rest)
}
```

`enquire`で選択した`workout`, `rest`を`setTimeout` に渡す。

```
function intervalTimer(selectWorkout) {
  return new Promise(resolve => {
      setTimeout(() => {
        const startEffect = player.play('start_effect.mp3')
        resolve()
      }, selectWorkout * 1000)
    }
  )
}

```

`async/await` を使って同期処理で実行。

```
async function running(setNumber, workout, rest){
await log('\x1b[32m10 seconds before Workout')
await firstWorkout(10)
for (let i = 0; i < setNumber; i++) {
log(emoji.get('fist') + ` Set${i + 1} Workoout Start ` + emoji.get('fist'))
await intervalTimer(workout)
log((emoji.get('relaxed')) + ` Set${i + 1} Break ` + (emoji.get('relaxed')))
await intervalTimer(rest)
}
await log((emoji.get('congratulations')) + ' Workout Finish' + (emoji.get('exclamation')) + 'Good Job' + (emoji.get('exclamation')) + (emoji.get('congratulations')))
}
```
これでHIITのタイマー機能を実装することができました！

## 改善したい点
最後に改善したい点について書きます。

アドバイス等がありましたらコメントをいただけると嬉しいです！

#### `node_modules/hiit_timer`のディレクトリに移動しなければ使えない

作成したnpmは`mp3`を使用しているので、`mp3`が入ってる`node_modules/hiit_timer`に移動しないと使えないです。。

#### リファクタリングが不十分
もう少しスッキリしたコードになるんじゃないかと思うのですが、具体的にどうしたらいいのかわからず。。