【JavaScript】async / awaitって何？

# `await`って何？
[`await`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/await)は**プロミスの履行を待つ**構文です。
これを使うと、`Promise`の`then`をよりすっきりとしたコードに置き換えることができます。

`await`を使わずに**プロミスの結果を使いたい**とき、通常は`then`メソッドを使います。
順番に実行した非同期処理が複数あるなら、`then`をチェインしていました。

```js
fetch('https://example.com') // HTTPリクエストを送信
  .then(res => res.text()) // レスポンスボディを取得
  .then(text => console.log(text)) // 出力
```

`await`を使うと、このコードを以下のように書くことができます。

```js
// Responseオブジェクトが入る
const response = await fetch('https://example.com')

// レスポンスボディを取得する
const body = await response.text()
console.log(body) // 出力する
```

## 詳細
`await`は式の左に置くことができます。
また、`await`の右に`Promise`オブジェクトを置くことで、そのプロミスの履行結果を取得できます。

```js
const promise = // Promiseオブジェクト
const result = await promise
```

結果の取得に時間がかかる場合、`await`以降の処理はその時間が経ってプロミスが履行されてから実行されます。
そのため`await`を書いた部分の実行には**時間がかかる**ことがあります。

なお、このようにプロミスを直接置くより、プロミスを返す関数を書くことが多いです。

```js
const result = await プロミスを返す関数()
```

# `async`って何？
[`async`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/async_function)は関数の前につけることができるキーワードです。
`function`の前につけたり、アロー関数の前につけることもできます。

```js
// 関数宣言の前にasyncをつける
async function name() {
  // 処理
}
```

そして、**`await`は`async`がついた関数の中でしか使えません。**
例えば、次のコードは構文エラーとなります。

```js
// asyncがない関数
function fetchExample() {
  return await fetch('example.com')
}
```

このエラーを修正するには、関数の前に`async`をつける必要があります。

```js
// 先頭にasyncをつける
async function fetchExample() {
  return await fetch('example.com')
}
```

:::note
コードのトップレベルなら[Top-Level Await](https://shinshin86.hateblo.jp/entry/2021/10/10/071600)という仕様があるので、例外的に使えます。
:::


## 戻り値

`async`がついている関数は**戻り値がプロミスになります。**

```js
async function example() {
  console.log('Hello World!')
}

example() // Promise
// Hello World! <- console.logは実行される
```

このプロミスは関数内の処理が終わったとき（最後の`await`が履行されたとき）に履行されます。

```js
async function fetchExample() {
  const res = await fetch('example.com')
  const text = await res.text() // textはstringになる
  return text
}

fetchExample() // Promiseが返ってくる

await fetchExample() // string
```

:::note
もし`async`をつけた関数内に一つも`await`がない、かつ関数がプロミスを返さない場合、このプロミスは即座に履行されます。
:::

わざわざ`async`をつける理由として、この挙動が挙げられると思います。
もし`async`がなかったとして、`await`をつけた処理があるだけで関数の戻り値が勝手にプロミスになるのは問題です。

そのため明示的に`async`をつけ、この中では非同期処理が実行されているよ、この関数はプロミスを返すよ、というのが一目でわかるようにします。

# おまけ

## エラーハンドリング
プロミスでの処理内で発生したエラーは、通常のエラーとは違って`try-catch`で捕捉できません。
その代わりプロミスの`catch`メソッドで捕捉できます。

```js
function getPromise() {
  // 処理内でエラーが発生するプロミス
  return new Promise(() => {
    throw new Error('Error!')
  })
}

// try-catchの場合
try { 
  getPromise() 
} catch (e) {
  console.log('エラーをキャッチ')
}
// Uncaught (in promise) Error: Error!
// エラーをキャッチできていない

// catchメソッドの場合
getPromise().catch(e => console.log('エラーをキャッチ'))
// エラーをキャッチ
```

### `await`の場合
しかし、関数の前に`await`をつけると話は変わってきます。
`await`をつけたプロミス内で発生したエラーは、なんと通常の`try-catch`で捕捉できます。

```js
try {
  await getPromise() // awaitをつける
} catch (e) {
  console.log('エラーをキャッチ')
}
// エラーをキャッチ
```

### `catch`メソッドと`await`
関数の呼び出しの後に`catch`メソッドを繋げ、それを`await`してもエラーは捕捉できます。

```js
try {
  // awaitをつける & catchメソッドでハンドリング
  await getPromise()
    .catch(e => console.log('エラーをキャッチ in catchメソッド'))
} catch (e) {
  console.log('エラーをキャッチ')
}
// エラーをキャッチ in catchメソッド
// try-catchのcatch句内での処理は実行されない
```

```js
// 普通にこれだけでもOK
await getPromise()
  .catch(e => console.log('エラーをキャッチ in catchメソッド'))
// エラーをキャッチ in catchメソッド
```

しかし、処理の流れがどうなるのかが**直感的ではない**ので、特別な理由がない限りはやるべきではないと思います。

### 何が推奨？
非同期処理内でのエラーを補足するとき、推奨度合いは大まかに以下の表のようになると思います。

| 呼び出し方法 | エラーの補足方法 | 推奨度合い
|:--|:--|:--|
`await`での呼び出し | `try-catch` | 推奨
通常の呼び出し | `catch`メソッド | OK（NG派もいる）
`await`での呼び出し | `catch`メソッド | わかりづらいので非推奨
通常の呼び出し | `try-catch` | 絶対にNG: そもそも補足できない

とは言ったものの、最後を除いては人によると思います。

## プロミスを返す関数に`async`をつけたら？

`async`をつけなくともプロミスを返す関数は定義できます。
例えば`Promise`のコンストラクタからプロミスを作成した場合です。

```js
function getPromise() {
  return new Promise((resolve, reject) => {
    // 非同期で実行したい処理
  })
}
```

現在の`getPromise`関数の戻り値は、コード内の`new Promise`で作ったプロミスと同じです。

では、この関数の先頭に`async`をつけたらどうなるでしょうか。

`async`をつけた関数は、コード内で`new`されたプロミスとは別の**新しいプロミス**を返します。
これはコード内のプロミスをラップしたものなので、実際に呼び出したときの動作は変わりません。

```js
async function getPromiseAsync() {
  // このプロミスをラップしたプロミスが戻り値になる
  return new Promise((resolve, reject) => {
    // 非同期で実行したい処理
    resolve()
  })
}

// 関数の動作は変わらない
await getPromise()
await getPromiseAsync()
```

詳細は[MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/async_function#解説)をご覧ください。