カウントダウン処理でawaitとasyncの使い方を学ぶ

##async/awaitとは
非同期の処理を同期的に書くために使うことは大体の人が知っていると思います。
しかしそれをいざ使ってと言われると私のような初心者は困ってしまいます。
そんな人達にのためにこの記事を書きました。
カウントダウン処理のコードを書きながらawait/asyncの使い方を学んでいきます。

##したいこと（目標）
カウントダウン処理ををawait/asyncを使って実装します。
具体的には以下のようにコンソールで表示できるようにします。
`start, 5, 4, 3, 2, 1, next`

##プラン
これからやることは
- start関数を作る
- next関数を作る
- countdown関数を作る
- それぞれが意図したとおりの順番に動くように調整する
です。

##start関数とnext関数を作る
コンソールに文字を表示するだけなので、とても簡単です。

```javascript
console.log("start");
//この間にカウントダウンの関数を入れたい
console.log("next");
```

<script async src="//jsfiddle.net/cedric_tarou/5zjqe7xs/4/embed/js,html,css,result/dark/"></script>

[結果](https://jsfiddle.net/cedric_tarou/5zjqe7xs/6/)

このままだとイマイチなので関数として呼び出せるようにします。

```javascript
//all関数を実行する
all();
//goとnextの関数をまとる関数を作る
function all() {
  start();
  //この間にカウントダウンの関数を入れたい
  next();
}

function start() {
  console.log("start");
}

function next() {
  console.log("next");
}
```

[結果](https://jsfiddle.net/cedric_tarou/5zjqe7xs/66/)

##カウントダウン関数を作る
###とりあえずsetTimeoutを使って見る
今回はsetTimeoutを使用してつくります。
とりあえず１秒後にカウントダウンとコンソールで表示されるようにします。

```javascript
function countdown() {
	setTimeout(() => {
  	console.log("countdown");
  }, 1000)
}
```
<script async src="//jsfiddle.net/cedric_tarou/5zjqe7xs/19/embed/js,result/dark/"></script>
[結果](https://jsfiddle.net/cedric_tarou/5zjqe7xs/67/)

今のままでは結果が以下のようになります。

```console
"start"
"next"
"countdown"
```

###async/awaitを使って同期的な処理にする
start, countdown, nextとなって欲しいのでここでようやくasync/awaitを使います。

```javascript
async function all() {
  start();
  await countdown();
  next();
}
```

###async/awaitはpromiseが返ってくる必要がある！
しかし、これではまだ駄目です。
単純にasync/awaitをつければいいわけではありません。(私はそう思っていました。)
async/awaitを使うにはpromiseが返ってくるようにする必要があります。
そのために、度のタイミングでpromiseが返ってくるかをcountdown関数に書き込みます。

```javascript
function countdown() {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log("countdown");
      resolve() //１秒後に”countdown”が出たらresolve状態になるという合図
    }, 1000)
  })
}
```

[結果](https://jsfiddle.net/cedric_tarou/5zjqe7xs/27/)

これでコンソールに正しい順番で表示されます。

```
"start"
"countdown"
"next"
```
[Promiseとasyncの関係はこちらを参照してください。](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/async_function)

###カウントダウンされるようにコードを書く
続いて4，3，2，1，0とカウントダウンされるように処理します。
ここからは少しややこしくなります。

```javascript
all();

async function all() {
  start();
  await countdown(5); //secの初期値を５にしておく
  next();
}

function start() {
  console.log("start");
}

function next() {
  console.log("next");
}

function countdown(sec) {
  return new Promise(resolve => {
    console.log(sec)
    sec -= 1 //secの値を１ずつ減らしていく
    setTimeout(() => {
      countdown(sec); //１秒毎にcountdownが何度も呼び出される処理
      resolve()
    }, 1000)
  })
}

```

[結果](https://jsfiddle.net/cedric_tarou/5zjqe7xs/68/)

こうすると値は5,4,3,2,1となっていきます。

###clearTimeoutを使ってsetTimeoutの処理を止める
しかし、これでは０以下にもどんどんと値が続いてしまうのでclearTimeoutを使ってsecの値が０になったら処理が止まるようにします。

```javascript
function countdown(sec) {
  return new Promise(resolve => {
    console.log(sec)
    sec -= 1
    const timeId = setTimeout(() => {
      countdown(sec);
      resolve();
    }, 1000)
    if(sec === 0) { //secの値が０になったら処理
    	clearTimeout(timeId);
    }
  })
}
```

[結果](https://jsfiddle.net/cedric_tarou/5zjqe7xs/70/)


０で止まるようにありましたが、このままでは以下の通りカウントダウンの途中で次の処理が行われてしまいます。

```console
"start"
5
4
"next"
3
2
1
```
###再びasync/awaitの登場
ここでの問題はcountdown関数が一度回ったあと（secの値が5から4に変わる時）next関数が実行されることにあるようです。
setTimeoutは非同期を行うための処理です。なのでsetTimeoutを同期的な処理に変える必要があります。そのためにここでまたasync/awaitが必要になってきます。

```javascript
function countdown(sec) {
  return new Promise(resolve => {
    console.log(sec);
    sec -= 1;
    const timeId = setTimeout(async () => {
      await countdown(sec);
      resolve();
    }, 1000)
    if (sec === 0) {
      clearTimeout(timeId);
    }
  })
}
```
とするとsetTimeoutの処理を一回ごとに同期的な処理にしてくれます。（1秒ごとにresolveが返ってくるまで待つということ）

[結果](https://jsfiddle.net/cedric_tarou/5zjqe7xs/60/)

コンソールでは以下のように表示されます。

```console
"start"
5
4
3
2
1
```

今度はカウントが1まで言っているのにnext関数が呼び出されていません。

###resolveが呼び出されないと次の処理にはいけない！
これはsecが0になったときにresolveが返っていないので、次の処理が行われないためです。
なのでresolveを追加すればOKのハズです。

```javascript
function countdown(sec) {
  return new Promise(resolve => {
    console.log(sec)
    sec -= 1
    const timeId = setTimeout(async () => {
      await countdown(sec);
      resolve();
    }, 1000)
    if (sec === 0) {
      clearTimeout(timeId);
      resolve(); ///このタイミングでresolveがかえるようにする
    }
  })
}
```
[結果](https://jsfiddle.net/cedric_tarou/5zjqe7xs/64/)

こうすることで意図したとおりの動作をしてくれます。

```console
"start"
5
4
3
2
1
"next"
```

##まとめ
ながながと解説してきましたが、これで終わりです。
単純なカウントダウンを作るだけなら特に困らなかったと思いますが、それを前後別の動作と組み合わせるとなかなかややこしいなと思ったのでまとめてみました。
ポイントは"async/awaitだけではなくresolveが返ってくるタイミングを考える"です。
非同期の処理を同期的な処理にするにはasync/awaitが必要ですが、それだけではなく、いつまで処理を待てばいいのかを教える「resolveを返す」という処理をセットで考える必要があります。
特にsetTimeoutのなかにasync/awaitを使うという発想がなかったので備忘録を兼ねて記事にしてみました。
皆さんのお役に立てば幸いです