[JavaScript] 非同期処理のPromise, async, awaitサンプル多めで丁寧にわかりやすく

# Promiseとか非同期処理が意味不明
普段プログラミングで書くのは同期処理です。
コードを順番に上から下に実行していって、途中時間のかかる処理の間は動作・画面が途中で止まることになります。

![スクリーンショット 2022-06-07 20.41.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/d855e5cf-dcdb-6cd2-a377-c68541839fa5.png)

例えば時間かかる処理としてはサーバーと通信して画像とか動画とかのサイズ大きいデータを取得する処理とかですね。
画像を表示しようとするたびに画面が固まって操作を受け付けないページとかあったら、イライラしてさっさと戻るボタン押しますよね。
<img width="400" alt="iraira" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/977875e1-e1a9-d8f1-2277-5fcde05bf4d0.png">


このような場合JavaScriptで使うのは非同期処理です！
非同期処理はコードを順番に上から下に実行していくけど、時間がかかる処理は「あとで処理するよ〜」って感じで<font color="red">**予約だけ**</font>して次の処理に移らせることができます。

![スクリーンショット 2022-06-07 21.08.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/5a856f52-9e5e-438a-1e5e-7f507622e836.png)

このようにすればページ全体を表示するような処理はささっと終了させて、あとから画像が読み込まれて表示されるなんてことができますね。

# 処理の予約ってどうやるのか？
非同期処理で処理の<font color="red">**予約**</font>だけして実際には処理はせずに、次に進むと書きました。
この処理の予約にはJavaScriptは`関数を登録`して実現します。

関数を登録(セット)して後で実行するのを<font color="red">**コールバック**</font>と言います。
JavaScriptはこのコールバックを多用します。

- ボタンをクリックしたときに行う処理(コールバック)をあらかじめセット。実際に押されたら処理(コールバック)が実行される
- ファイルを選択フォームで読み込み完了した時に行う処理(コールバック)をあらかじめセット。ファイルが読み込めたら行う処理を実行


などなどイベントリスナーとして色々あります。

# 1. コールバックの罠、コールバック地獄
前置き長かったですのでそろそろコード書きます！
こんなコールバック普通はないと思いますけど数秒ごとに文字が出力されるコードです。

```.js
const butotnClick = () => {
  setTimeout(function () {
    console.log('処理1');
    setTimeout(function () {
      console.log('処理2');
      setTimeout(function () {
        console.log('処理3');
        setTimeout(function () {
          console.log('処理4');
          setTimeout(function () {
            console.log('処理5');
            setTimeout(function () {
              console.log('処理6');
            }, 2000);
          } ,1000);
        }, 2000);
      } ,1000);
    }, 2000);
  }, 1000);
}
```
console.logだけだと使い道などないのでコード少し改造して一定間隔で`<li>`要素を追加するコードを書きましたのでよければお試しください。

<p class="codepen" data-height="299.8203125" data-default-tab="js,result" data-slug-hash="oNEPpoG" data-user="kohishibashi" style="height: 299.8203125px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/kohishibashi/pen/oNEPpoG">
  callback hell</a> by kohishibashi (<a href="https://codepen.io/kohishibashi">@kohishibashi</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>


これが<font color="red">**コールバック地獄**</font>。

**コールバック**の中に
**コールバック**があって、
**コールバック**がその中にあって、
**コードバック**がさらにある、、、

という複雑な入れ子状態です。

このサンプルでも最低限の処理しか書いていないのでエラーハンドリングや途中でデータの加工などをすることになり、人間には読めたもんじゃなくなります:sob:


ここでようやく登場するのが<font color="red">**Promise**</font>。こんな読みにくい書き方を改善するために導入されました。

# 2. <font color="red">Promise</font>使えば入れ子構造→順番に書ける

先ほどのコードバックをもっと読みやすくしたい！ということで使われるのが<font color="red">Promise</font>です。このPromiseの`then`を使うと入れ子構造を順番に書くことができます。
例えばこんな感じ

:::note
先ほどのコンソールに出力するサンプルを省略しないで忠実に書いてみるので少し長くなります💦
:::

```.js
new Promise(function(resolve) {
  setTimeout(function() {
    console.log('処理1');
    resolve();
  }, 1000)
})
.then(function() {
  return new Promise(function(resolve) {
    setTimeout(function() {
      console.log('処理2');
      resolve();
    }, 2000)
  })
})
.then(function() {
  return new Promise(function(resolve) {
    setTimeout(function() {
      console.log('処理3');
      resolve();
    }, 1000)
  })
})
.then(function() {
  return new Promise(function(resolve) {
    setTimeout(function() {
      console.log('処理4');
      resolve();
    }, 2000)
  })
})
.then(function() {
  return new Promise(function(resolve) {
    setTimeout(function() {
      console.log('処理5');
      resolve();
    }, 1000)
  })
})
.then(function() {
  return new Promise(function(resolve) {
    setTimeout(function() {
      console.log('処理6');
      resolve();
    }, 2000)
  })
})
```
<p class="codepen" data-height="300" data-default-tab="js,result" data-slug-hash="XWZPEKo" data-user="kohishibashi" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/kohishibashi/pen/XWZPEKo">
  [Promise] callback hell</a> by kohishibashi (<a href="https://codepen.io/kohishibashi">@kohishibashi</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>


記述は長くはなりましたが、意味不明な入れ子構造からは卒業して上から順に実行されていくのがわかりやすくなったと思います。


- 最初、Promiseオブジェクトを作る
- Promiseオブジェクトは`.then()`を順番に繋げて書くことができる。
- returnにPromiseオブジェクトを書いているので次も`then()`を使える
- 一つ一つ処理の終了を待って処理を進めている
- `resolve()`はその関数の処理の終了させるもの
- `resolve()`実行したら`then()`に処理が移る

:::note
ついでにthen()の中でPromiseをreturnしない場合待たないですぐ次の`then()`を実行する
:::

## 2.2 冗長な書き方をリファクタリング
正直さっきのコードでは長すぎてPromise使えねーという感じなので、重複している箇所をまとめてアロー関数も使って書き直してみます。

```.js
const addElementWithTimer = (msec, text) => new Promise(
  resolve => {
    setTimeout(() => {
      console.log(text);
      resolve();
    }, msec)
  }
)

addElementWithTimer(1000, '処理1')
  .then(() => addElementWithTimer(2000, '処理2'))
  .then(() => addElementWithTimer(1000, '処理3'))
  .then(() => addElementWithTimer(1000, '処理4'))
  .then(() => addElementWithTimer(1000, '処理5'))
  .then(() => addElementWithTimer(1000, '処理6'))
```


<p class="codepen" data-height="300" data-default-tab="js,result" data-slug-hash="xxYaWJb" data-editable="true" data-user="kohishibashi" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/kohishibashi/pen/xxYaWJb">
  [Promise2] callback hell</a> by kohishibashi (<a href="https://codepen.io/kohishibashi">@kohishibashi</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

ここまで書くとコールバック地獄は消えて順番に処理していくのがわかりやすくなると思います。

# 3. 次の処理に引数を渡す
今回くらいの処理なら普通は使わないですが、`then`で順番に処理をしていく時に次の`then`の処理に値を渡したい場合があります。

※ 例えば一つ目でAPIからデータ取得して次のthenの中でデータを使って表示を変えるとか。

これは単純で`resolve()`に引数を与えれば次のthenに値を渡すことができます。

```.js
const addElementWithTimer = (msec, text, id) => new Promise(
  resolve => {
    setTimeout(() => {
      console.log(`${text}${id}`);
      id++
      resolve(id);
    }, msec)
  }
)

addElementWithTimer(1000, '処理', 1)
  .then(id => addElementWithTimer(2000, '処理', id))
  .then(id => addElementWithTimer(1000, '処理', id))
  .then(id => addElementWithTimer(1000, '処理', id))
  .then(id => addElementWithTimer(1000, '処理', id))
  .then(id => addElementWithTimer(1000, '処理', id))
```

```
> "処理1"
> "処理2"
> "処理3"
> "処理4"
> "処理5"
> "処理6"
```

`resolve`の引数に`id`を入れることで次の`then`に値を渡しています。
これを繰り返してバケツリレーのように渡すように改造してます。

※ 渡す値が同じだと面白くないのでidに1を足して渡してます。

# 4. async await
読み方は`エイスィンク` `アウェイト`です。

最初`アスィンク`と読んでいたら`エイスィインク`だよ。
って言われて今度は`エイウェイト`だと思ってたら、こっちは普通だったというどうでもいい思い出があります:innocent:

## 4.1 asyncを関数の前につけるとその関数はawait使える
MDNによると

>非同期関数は async キーワードで宣言され、その中で await キーワードを使うことができます。 async および await キーワードを使用することで、プロミスベースの非同期の動作を、プロミスチェーンを明示的に構成する必要なく、よりすっきりとした方法で書くことができます。

うーむわからない。
とりあえず先ほどまで出していた例を書き換えてみる

```.js
const addElementWithTimer = (msec, text, id) => new Promise(
  resolve => {
    setTimeout(() => {
      console.log(`${text}${id}`);
      id++
      resolve(id);
    }, msec)
  }
)

const butotnClick = async () => {
  const res1 = await addElementWithTimer(1000, '処理', 1)
  const res2 = await addElementWithTimer(2000, '処理', res1)
  const res3 = await addElementWithTimer(1000, '処理', res2)
  const res4 = await addElementWithTimer(1000, '処理', res3)
  const res5 = await addElementWithTimer(1000, '処理', res4)
  const res6 = await addElementWithTimer(1000, '処理', res5)
  
  console.log(`処理終了: ${res6}`)
}

butotnClick()
```

これを実行すると

```
> "処理1"
> "処理2"
> "処理3"
> "処理4"
> "処理5"
> "処理6"
> "処理終了: 7"
```
console.logの表示は↓のような埋め込みのcodepenだと出ないので、気になる人はcodepenの本体ページいってみてください。

<p class="codepen" data-height="300" data-default-tab="js,result" data-slug-hash="zYRJjyZ" data-editable="true" data-user="kohishibashi" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/kohishibashi/pen/zYRJjyZ">
  Untitled</a> by kohishibashi (<a href="https://codepen.io/kohishibashi">@kohishibashi</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

## 4.2 何が変わったのか？
`async`を<font color="red">関数の前</font>におくとその関数で<font color="red">Promiseを返す関数は便利な書き方を使える</font>ようになります。

```.js
const sampleFunc = async function() {
// ここからawait使うと便利な書き方になる
// ...
// ...
// この関数内だけ
}
```

- addElementWithTimerはPromiseを返す関数
    - [必要なら少し上のコードで確認してください](#41-asyncを関数の前につけるとその関数はawait使える)
- awaitを使うと同期処理のコード書いてるかのように書ける
- await使うとthen使うよりもreturnで戻ってくる値が直感的にすごくわかりやすい

```.js
// Before
const butotnClick = () => {
addElementWithTimer(1000, '処理', 1)
  .then(id => addElementWithTimer(2000, '処理', id))
  .then(id => addElementWithTimer(1000, '処理', id))
  .then(id => addElementWithTimer(1000, '処理', id))
  .then(id => addElementWithTimer(1000, '処理', id))
  .then(id => addElementWithTimer(1000, '処理', id))
}

// After 書いていることは同じ
const butotnClick = async () => {
  const res1 = await addElementWithTimer(1000, '処理', 1)
  const res2 = await addElementWithTimer(2000, '処理', res1)
  const res3 = await addElementWithTimer(1000, '処理', res2)
  const res4 = await addElementWithTimer(1000, '処理', res3)
  const res5 = await addElementWithTimer(1000, '処理', res4)
  const res6 = await addElementWithTimer(1000, '処理', res5)
}
```

### 4.2.2 実行順番注意
awaitをつけていない箇所の実行の順番も変わるので注意してください。

```.js
const addElementWithTimer = (msec, text, id) => new Promise(
  resolve => {
    setTimeout(() => {
      console.log(`${text}${id}`);
      id++
      resolve(id);
    }, msec)
  }
)

const butotnClick1 = () => {
  console.log('開始')
  addElementWithTimer(1000, '処理', 1)
  .then(id => addElementWithTimer(2000, '処理', id))
  .then(id => addElementWithTimer(1000, '処理', id))
  .then(id => addElementWithTimer(1000, '処理', id))
  .then(id => addElementWithTimer(1000, '処理', id))
  .then(id => addElementWithTimer(1000, '処理', id))
  console.log('終了')
}


const butotnClick2 = async () => {
  console.log('開始')
  const res1 = await addElementWithTimer(1000, '処理', 1)
  const res2 = await addElementWithTimer(2000, '処理', res1)
  const res3 = await addElementWithTimer(1000, '処理', res2)
  const res4 = await addElementWithTimer(1000, '処理', res3)
  const res5 = await addElementWithTimer(1000, '処理', res4)
  const res6 = await addElementWithTimer(1000, '処理', res5)
  console.log('終了')
}
```

`butotnClick1()`, `butotnClick2()`を実行すると


```
# butotnClick1() then()使ったパターン
> "開始"
> "終了"
> "処理1"
> "処理2"
> "処理3"
> "処理4"
> "処理5"
> "処理6"
```

```
# butotnClick2() async await使ったパターン
> "開始"
> "処理1"
> "処理2"
> "処理3"
> "処理4"
> "処理5"
> "処理6"
> "終了"
```

butotnClick1は「終了」がすぐにきています。同期処理のconsole.logは先に処理が終わっていて、非同期処理の箇所が遅れて実行されるからこうなっています。
butotnClick2は同期処理っぽく上から順番に処理してくれていてしっくりきますね。


# 5. Promiseのエラー処理reject
これまでは処理が成功するパターンだけを見てきました。
処理がうまくいかなかった場合に使うのが`reject`です。

- 処理成功の`resolve`を第一引数
- 処理失敗の`reject`は第二引数

にとります。以下は同じコードで書き方が違うだけです。

## 5.1 二つ目の関数を書いてエラー処理する方法
まずは関数を二つ書く書き方。

```.js
const addElementWithTimer = (msec, text, id) => new Promise(
  (resolve, reject) => {
    setTimeout(() => {
      console.log(`${text}${id}`);
      id++
      if (id === 2) reject(id);
      resolve(id);
    }, msec)
  }
)

const butotnClick1 = () => {
  console.log('開始')
  addElementWithTimer(1000, '処理', 1)
  .then(
    id => addElementWithTimer(2000, '処理', id),  // resolveの処理
    id => { console.log(`ID: ${id} 失敗...`) }  // こっちがreject
  )
}
// 実行
butotnClick1()
```
`id === 2`の時に`reject`させます。
これを実行すると

```
> "開始"
> "処理1"
> "ID: 2 失敗..."
```

`"処理2"`とはならずに2個目の処理が実行されました。

:::note
引数のidを渡せるのはresolveと同じ。
:::

## 5.2 catchを使う方法
どちらかといえばこちらの方が便利にかける。`catch`を使えば`reject()`されたら`catch`に処理が移ります。

```.js
const addElementWithTimer = (msec, text, id) => new Promise(
  (resolve, reject) => {
    setTimeout(() => {
      console.log(`${text}${id}`);
      id++
      if (id === 2) reject(id);
      resolve(id);
    }, msec)
  }
)
const butotnClick1 = () => {
  console.log('開始')
  addElementWithTimer(1000, '処理', 1)
  .then(id => addElementWithTimer(2000, '処理', id))
  .then(id => addElementWithTimer(1000, '処理', id))
  .catch(id => { console.log(`ID: ${id} 失敗...`) })
}
// 実行
butotnClick1()
```
`catch`を追加しています。このように書いた場合２個目の`then()`でrejectが起こるのでcatchに処理が移ります。
これを実行すると

```
> "開始"
> "処理1"
> "ID: 2 失敗..."
```

### ついでに
一応catchの説明は以上ですが、いろんなサンプル書いてみます。文章読むよりも手を動かした方が理解早いのでこの他にも少し改造して動かしてみてください。

#### catchの後にthen

```.js
const butotnClick1 = () => {
  console.log('開始')
  addElementWithTimer(1000, '処理', 1)
  .then(id => addElementWithTimer(2000, '処理', id))
  .catch(id => { 
    setTimeout(() => {
      console.log(`ID: ${id} 失敗...`) ;
    }, 1000);
  })
  .then(() => { console.log('これはすぐ実行される') })
}
```

これを実行すると`catch`で1秒待ってから`then`を実行してくれそうだけどすぐ実行されるから注意

```
> "開始"
> "処理1"
> "これはすぐ実行される"
> "ID: 2 失敗..."
```

次の`then`に移るためには、`Promiseオブジェクト返す`のと、`resolve()`しないといけないのでこのように書く必要がある。

```.js
const butotnClick1 = () => {
  console.log('開始')
  addElementWithTimer(1000, '処理', 1)
  .then(id => addElementWithTimer(2000, '処理', id))
  .catch(id => { 
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log(`ID: ${id} 失敗...`) ;
        resolve();
      }, 1000);
    });
  })
  .then(() => { console.log('Promiseが欲しかった') })
}
```
return省略できる書き方もあるけどわかりやすさ優先で書きました。

```
> "開始"
> "処理1"
> "ID: 2 失敗..."
> "Promiseが欲しかった"
```

#### 関数二つ使ったパターンにcatchも使う
引数二つと`catch`を同時に使うと`catch`は使用されない。

```.js
const butotnClick1 = () => {
  console.log('開始')
  addElementWithTimer(1000, '処理', 1)
  .then(
    id => addElementWithTimer(2000, '処理', id),  // resolveの処理
    id => { console.log(`ID: ${id} 失敗...`) }  // こっちがreject。これが使われる
  )
  .catch(id => { console.log(`catch実行されないよ`)  })
}
```

```
> "開始"
> "処理1"
> "ID: 2 失敗..."
```

# 6. Promiseの成功しても失敗しても実行する`finally`
これまで処理が成功、失敗する場合のPromiseの書き方を見てきました。
実はもう一つあって、成功しても失敗しても実行する`finally`というものがあります。

## finallyを使ってみる
`resolve()`されて終了しても、`reject()`されて終了しても最後に処理を行ってくれる`finally`の例を見ていきます。

```.js
const addElementWithTimer = (msec, text, id) => new Promise(
  (resolve, reject) => {
    setTimeout(() => {
      console.log(`${text}${id}`);
      id++
      if (id === 2) reject(id);
      resolve(id);
    }, msec)
  }
)
const butotnClick1 = () => {
  console.log('開始')
  addElementWithTimer(1000, '処理', 1)
  .then(id => addElementWithTimer(2000, '処理', id))
  .then(id => addElementWithTimer(1000, '処理', id))
  .catch(id => { console.log(`ID: ${id} 失敗...`) })
  .finally(()=> { console.log('処理終了！') })
}
// 実行
butotnClick1()
```

```
> "開始"
> "処理1"
> "ID: 2 失敗..."
> "処理終了！"
```
処理が失敗して最後に`finally`の処理が実行されて終了してますね。


次にrejectを消して、catchにならないように実行してみると

```.js
const addElementWithTimer = (msec, text, id) => new Promise(
  (resolve, reject) => {
    setTimeout(() => {
      console.log(`${text}${id}`);
      id++
      resolve(id);
    }, msec)
  }
)
const butotnClick1 = () => {
  console.log('開始')
  addElementWithTimer(1000, '処理', 1)
  .then(id => addElementWithTimer(2000, '処理', id))
  .then(id => addElementWithTimer(1000, '処理', id))
  .catch(id => { console.log(`ID: ${id} 失敗...`) })
  .finally(()=> { console.log('処理終了！') })
}
```

```
> "開始"
> "処理1"
> "処理2"
> "処理3"
> "処理終了！"
```

`then`が全部終わって、最後に`finally`の処理が実行されて終了してますね。
用途としては処理の後処理なんかに使えます。例えば読み込み中のグルグルと回る「通信中表示」を消すとか。


# 7. async awaitのエラー処理rejectは`try catch`
thenにcatchがあるなら<font color="red">**async await**</font>を使った書き方にも当然`catch`があります。
コードは`then`と`catch`を繋げて書いたときの内容と同じく、idが2になったらreject。引数にidを渡すコード。

```.js
const addElementWithTimer = (msec, text, id) => new Promise(
  (resolve, reject) => {
    setTimeout(() => {
      console.log(`${text}${id}`);
      id++
      if (id === 2) reject(id);
      resolve(id);
    }, msec)
  }
)

const butotnClick1 = async () => {
  console.log('開始')
  try {
    res1 = await addElementWithTimer(1000, '処理', 1);
    res2 = await addElementWithTimer(1000, '処理', res1);
    res3 = await addElementWithTimer(1000, '処理', res2);
  } catch(e) {
    console.log(`ID: ${e} 失敗...`)
  }
}
// 実行
butotnClick1()
```

```
> "開始"
> "処理1"
> "ID: 2 失敗..."
```

同期処理書く感じで書けるので超わかりやすいですね！

# 8. async awaitで`finally`
これは説明するまでもないかもしれませんが一応書きます。
さっきの例と同じく、idが2の時にrejectされるのでcatchに入り、最後に`finally`に入ります。

```.js
const addElementWithTimer = (msec, text, id) => new Promise(
  (resolve, reject) => {
    setTimeout(() => {
      console.log(`${text}${id}`);
      id++
      if (id === 2) reject(id);
      resolve(id);
    }, msec)
  }
)

const butotnClick1 = async () => {
  console.log('開始')
  try {
    res1 = await addElementWithTimer(1000, '処理', 1);
    res2 = await addElementWithTimer(1000, '処理', res1);
    res3 = await addElementWithTimer(1000, '処理', res2);
  } catch(e) {
    console.log(`ID: ${e} 失敗...`)
  } finally {
    console.log('処理終了！');
  }
}
// 実行
butotnClick1()
```

```
> "開始"
> "処理1"
> "ID: 2 失敗..."
> "処理終了！"
```

async awaitだと素直に同期処理っぽく書けるので`finally`書く必要あることがあまりないかもしれないですね。わざわざfinally書かなくても下のように書けます。

```.js
const butotnClick1 = async () => {
  console.log('開始')
  try {
    res1 = await addElementWithTimer(1000, '処理', 1);
    res2 = await addElementWithTimer(1000, '処理', res1);
    res3 = await addElementWithTimer(1000, '処理', res2);
  } catch(e) {
    console.log(`ID: ${e} 失敗...`)
  }

  console.log('処理終了！');
}
```

```
> "開始"
> "処理1"
> "ID: 2 失敗..."
> "処理終了！"
```

# 最後に
ここまでやればとりあえずコードを解読することはできるのではないかと思います！

これが理解できたら静的メソッドも勉強してみたらPromiseは大体問題ないかなと思います！

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise#%E9%9D%99%E7%9A%84%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89