非同期処理 Promise/async/await について簡単に説明【JavaScript】

# はじめに

JavaScriptに関する「非同期処理」「Promise」「async/await」について簡単にまとめました。

初心者の方にとっては少しとっつきにくいお話だと思いますが、こんな感じか〜くらいに理解してもらえれば嬉しいです。

# 概要

- 非同期処理とは（同期処理との比較）
- Promise
- async/await

## 非同期処理とは

非同期処理は字の如く「同期的ではない処理」となります。

同期的な処理とは、

1. Aの処理
1. Bの処理
1. Cの処理

と処理が順番に書かれている場合、上から順に処理が実行され、AとBの処理が完了した後にCの処理が実行されるようなものになります。

例えば、

```js
console.log('A');

// 3秒後に実行する処理
setTimeout(() => {
  console.log('B(3秒後に実行)');
}, 3000);

console.log('C');
```

このような処理があった時、A→3秒後にB→Cの順に処理されることが理想ですが、実際に動かしてみると

```
A
C
B(3秒後に実行)
```

というような順番で処理されています。

JavaScriptは非同期言語であり**処理を待つことができない**ため、前の処理に時間がかかった場合は、その処理の完了を待たずに後続処理が行われてしまうことが理由です。

ですが、後述する`Promise`や`async/await`を使うことでこのような非同期処理を同期的に扱うことができるようになります。（もちろん非同期的に処理させることも可能です）

## Promise

Promiseは日本語に訳すと「約束」となりますが、Promiseを使うことで処理の順序や後続処理をあらかじめ指定（約束）できるものだと考えてもらえれば大丈夫です。

下記のコードでは、new Promiseの第一引数のコールバック（ここではメソッドの引数に渡す関数のこと）の処理で`setTimeout()`を実行してます。

処理が完了したことを知らせるために処理の最後に`resolve()`を実行します。
こうすることで、resolve()が実行された後に、メソッドチェーンで処理されている`then()`メソッドの中身が実行されるようになります。

```ts
console.log('A');

// お約束を取り付けたい処理にPromise
new Promise((resolve) => {
    // 3秒後に実行する処理
    setTimeout(() => {
        console.log('B completed.');
        // 処理の完了を知らせる
        resolve('');
    }, 3000);
}).then(() => {
  // Bの処理の完了を待ってから実行される処理
  console.log('C');
});

```
```
A
B completed.
C
```

### Promiseには状態がある

Promiseには３つの状態があります。

- pending
    - **未解決**で、処理が完了するのを待っている状態
- resolved
    - **解決済み**で、処理が問題なく完了した状態
- rejected
    - **拒否**されたことで処理が失敗してしまった状態

以下に3パターンのコードを記述します。

### pending

```js
const promise = new Promise((resolve) => {});

console.log(promise);
```
結果
![スクリーンショット 2023-08-22 1.18.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1021235/21ba347e-5b41-b523-00f8-b847379bcd64.png)

この状態だとまだPromiseに対して何もしていないため、pendingになります。

### resolved

```js
const promise = new Promise((resolve) => {
    resolve('ここのメッセージが出るよ');
}).then((resolve) => {
    console.log(resolve);
});
```

結果
```
ここのメッセージが出るよ
```
resolveさせることができました。

また、resolve()メソッドの引数に渡した値は、then()のコールバック関数の引数として受け取ることができます。

### rejected

Promiseインスタンスの第二引数には`reject`を指定することができます。

```js
const promise = new Promise((resolve, reject) => {
    reject('エラー');
}).then((resolve) => {
    console.log(resolve);
}).catch((reject) => {
    console.log(reject);
});
```
結果
```
エラー
```
rejectさせることができました。

余談ですが、`Promise.all()`というメソッドがあり、引数にPromiseの配列を渡すことで、全ての処理がresolveしてから次の処理に進むといったことを実現できます。

```js
const promiseA = new Promise((resolve) => {
        setTimeout(() => resolve('promiseA completed.'), 3000);
});

const promiseB = new Promise((resolve) => {
        setTimeout(() => resolve('promiseB completed.'), 7000);
});

const promiseC = new Promise((resolve) => {
        setTimeout(() => resolve('promiseC completed.'), 5000);
});

Promise.all([promiseA, promiseB, promiseC])
    .then((resolve) => {
        console.log(resolve);
    });
```
結果
![スクリーンショット 2023-08-22 1.51.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1021235/4641eea6-cadd-31d6-f1b6-9ef69de2d66a.png)

結果も各Promiseのresolveを配列で受け取れます。

## async/await

- async
    - 非同期関数を定義する時に使用。関数の頭につけるとPromiseを返す関数を定義することができる。
- await
    - Promiseオブジェクトが値を返すのを待つための演算子。
    - async関数内でしか使用できない。

### async

下記のように非同期関数を定義する時に使用し、戻り値の型がPromise型になります。
Promise型はジェネリクスとなっているため、resolve()の引数に渡す型と揃えるようにします。

```js
const fetchData = async (): Promise<string> => {
  return new Promise((resolve) => {
      resolve('Data fetched!');
  });
}
```

上記で定義した`fetchData`というasync関数を実行していきたいと思いますが、
まずは`await`をつけずに実行してみます。

```js
const data = fetchData();
console.log(data);
```
![スクリーンショット 2023-08-22 2.00.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1021235/e297bb76-6a3d-fa5c-3a0f-851d30f98baf.png)
結果はこのようにpendingとなります。

Promiseで返ってきた値をそのまま`then()`することで、resolveさせることができます。

```js
const data = fetchData();
data.then((resolve) => console.log(resolve));
```
![スクリーンショット 2023-08-22 2.02.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1021235/89555dbb-1855-757b-c814-847e20ee3479.png)

では次に、`await`をつけて実行します。
```js
const data = await fetchData();
console.log(data);
```
![スクリーンショット 2023-08-22 2.02.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1021235/89555dbb-1855-757b-c814-847e20ee3479.png)
awaitをつけて実行することで、Promiseによって約束されている戻り値がそのまま返ってくるため、`data`変数から直接参照することができました。

<br>
<br>
<br>

こんなようにasync/awaitを使うことで、Promiseを使用した処理でthen()などを使用せずに、非同期処理を簡潔に書くことができます。

`try catch`によるエラーハンドリングも書き方にいくつかパターンがあり、

```js
try {
    await fetchData();
} catch (error: Exception) {
    console.log(error);
}
```

といったシンプルな書き方や、

```js
await fetchData().catch((error) => {
    console.log(error);
})
```

こんな感じで非同期処理ごとにエラーハンドリングを定義することもできます。
この辺は好みですね。

# 最後に

個人的にも曖昧だった非同期処理について見直す良い機会になったと思います。
最後まで読んでいただきありがとうございました。

参考記事

・[async/await 入門（JavaScript）](https://qiita.com/soarflat/items/1a9613e023200bbebcb3)
・[【ES6】 JavaScript初心者でもわかるPromise講座](https://qiita.com/cheez921/items/41b744e4e002b966391a)

Promiseの状態についてもっと詳しく知りたい方は↓
[fulfilled, rejected, settled...　Promiseの用語、全部分かりますか？](https://qiita.com/uhyo/items/a9f6e70f43287cc0f52e)