JavaScriptのPromiseを理解する

Promiseが導入される前は、コールバック関数を使って非同期処理を制御していました。
しかし、コールバック関数をつなげた場合、コードの階層が深くなり、可読性が下がってしまうという問題がありました。

そこで考えられたのが**Promise**です。

Promiseを使うことでより**直感的に非同期処理を記述できる**ようになります。

## Promiseとは
**非同期処理をより簡単に、可読性が上がるようにした記述方法です。**

Promiseは、以下の3つのステータスを持っています。

- Pending：初期の状態、または処理待ちの状態
- Fullfilled：処理が成功して完了した状態
- Rejected：処理が失敗した状態

Promiseオブジェクトのステータスによって、処理を分岐していきます。

## Promiseの構文
```javascript
new Promise((resolve,reject) => {
  // 同期処理
  resolve() // もしくは reject()
})
  .then(() => {
    // 非同期処理(resolveを待つ)
  })
  .catch(() => {
    // 非同期処理(rejectを待つ)
  })
  .finally(() => {
    // 非同期処理(then、またはcatchを待つ)
  });
```
Promiseの基本形です。`new Promise`でPromiseをインスタンス化します。インスタンス化したPromiseの**thenメソッド、catchメソッド、finallyメソッド**を使って、非同期処理に対して制御を加えていきます。

Promise構文の中では、`new Promise`の引数に与えたコールバック関数は同期処理されますが、**thenメソッドやcatchメソッド、finallyメソッドは全て非同期で実行されます。**

## Promiseの使い方（resolve, then）
```javascript
new Promise((resolve, reject) => {
  resolve('hello');
})
  .then((data) => {
    console.log(data); // hello
  })
  .catch(() => {
    // スキップされる
  })
  .finally(() => {
    console.log('end'); // end
  });
```
Promiseの引数として、コールバック関数を設定します。**コールバック関数は`resolve`と`reject`の2つの引数を取ります。**
`resolve`が呼ばれた場合は、thenメソッドの中のコールバック関数が実行され、このコールバック関数の引数には`resolve`で渡した実引数が渡ってきます。

thenメソッドが完了すると、catchメソッドはスキップされ、finallyメソッドに処理が移ります。finallyメソッドでは終了処理を記述します。

## Promiseの使い方（reject, catch）
```javascript
new Promise((resolve, reject) => {
  reject('bye');
})
  .then(() => {
    // スキップされる
  })
  .catch((data) => {
    console.log(data); // bye
  })
  .finally(() => {
    console.log('end'); // end
  });
```
`reject`は、**Promiseのコールバックでエラーが発生したときにPromiseに通知するために使用する関数**です。
`reject`が呼ばれた場合、catchメソッドの中のコールバック関数が実行され、引数には`refect`で渡した実引数が渡ってきます。

catchメソッドの中のコールバック関数が実行された後に、finallyメソッドに処理が移ります。

## Promiseの使い方（finally）
**finallyメソッドに渡したコールバック関数は必ず実行される**ため、共通の終了処理を記述します。
また、finallyメソッドの中で使用するコールバック関数は引数を使用することができません。

## Promiseの流れを確認する
ここからは、Promiseの流れを確認していきます。

```javascript
new Promise((resolve, reject) => {
  console.log('promise');
  resolve();
}).then(() => {
  console.log('then');
});

console.log('global');

// コンソール
// 1. promise
// 2. global
// 3. then
```

`global`が`then`よりも先に表示されます。thenメソッドに渡したコールバック関数は非同期処理なため、コールスタックが空になった後に実行されます。

https://qiita.com/cotton11aq/items/28e5d38f34a5eff888b2

## thenを繋げてみる
```javascript
new Promise((resolve, reject) => {
  console.log('promise');
  resolve();
})
  .then(() => {
    console.log('then');
  })
  .then(() => {
    console.log('then');
  })
  .then(() => {
    console.log('then');
  })
  .then(() => {
    console.log('then');
  });

console.log('global');

// コンソール
// 1. promise
// 2. global
// 3. then
// 4. then
// 5. then
// 6. then
```
`then`を繋げると、**thenメソッドに渡されたコールバック関数が順番に処理されます。**
コールバック関数を使った非同期処理と比べて、`then`を繋げても多階層になることはありません。

## catchメソッドを追加してみる
```javascript
new Promise((resolve, reject) => {
  console.log('promise');
  reject(); // resolve　→ rejectに変更
})
  .then(() => {
    console.log('then');
  })
  .catch(() => {
    console.log('catch');
  });

console.log('global');

// コンソール
// 1. promise
// 2. global
// 3. catch
```
`resolve`を`reject`に変更すると、thenメソッドはスキップされcatchメソッドに処理が移ります。
catchメソッドも非同期処理されるため、コンソールの一番最後に`catch`が表示されます。

## thenの中でcatchに処理を移行したい場合
`reject`は`new Promise`の引数に渡すコールバック関数内でしか使えません。
そのため、thenメソッドの中でcatchメソッドに処理を移行したい場合には、`throw`を使います。

```javascript
new Promise((resolve, reject) => {
  console.log('promise');
  resolve();
})
  .then(() => {
    console.log('then');
    throw new Error();
  })
  .then(() => {
    console.log('then');
  })
  .catch(() => {
    console.log('catch');
  });

console.log('global');

// コンソール
// 1. promise
// 2. global
// 3. then
// 4. catch
```
1回目の`then`は実行されますが、2回目の`then`は実行されません。
**throwでエラーが発生したことをPromiseに伝えることでcatchメソッドに処理が移行します。**

## finallyを追加してみる
`finally`は、`resolve`の場合はthenメソッドの後、`reject`の場合はcatchメソッドの後に必ず呼ばれます。

```javascript
new Promise((resolve, reject) => {
  console.log('promise');
  resolve();
})
  .then(() => {
    console.log('then');
  })
  .catch(() => {
    console.log('catch');
  })
  .finally(() => {
    console.log('finally');
  });

console.log('global');

// コンソール
// 1. promise
// 2. global
// 3. then
// 4. finally
```

## thenメソッド（catchメソッド）のコールバック関数に引数を設定する場合

```javascript
new Promise((resolve, reject) => {
  console.log('promise');
  resolve('hello');
})
  .then((data) => {
    console.log(`then: ${data}`);
    return data;
  })
  .then((data) => {
    console.log(`then: ${data}`);
    return data;
  })
  .catch((data) => {
    console.log(`catch: ${data}`);
  })
  .finally((data) => {
    console.log(`finally: ${data}`);
  });

console.log('global');

// コンソール
// 1. promise
// 2. global
// 3. then: hello
// 4. then: hello
// 5. filally: undefined
```
1つ目のthenメソッドの`data`に値を渡すためには`roselve`に引数を渡す必要があります。この例で言うと、`hello`がthenメソッドの引数として設定されます。

2つ目のthenメソッドの`data`に値を渡すためには**1つ目のthenメソッドの中で`return`を使用します。**
ただし、**finallyは引数を取れない**のでthenメソッドの中でretrunを使用してもundefindとなります。

上記同様、`reject`が呼ばれた場合はcatchメソッドに処理が移り、`data`で値を受け取れます。

```javascript
new Promise((resolve, reject) => {
  console.log('promise');
  reject('bye');
})
  .then((data) => {
    console.log(`then: ${data}`);
    return data;
  })
  .then((data) => {
    console.log(`then: ${data}`);
    return data;
  })
  .catch((data) => {
    console.log(`catch: ${data}`);
  })
  .finally((data) => {
    console.log('finally');
  });

console.log('global');

// コンソール
// 1. promise
// 2. global
// 3. catch: bye
// 4. finally
```

## PromiseにsetTimeout（非同期関数）を使ってみる

Promoseを使うと`resolve`か`reject`が呼ばれるまではthenやcatchが呼ばれません。
そのため、Promiseのコールバック関数に非同期関数を組み込むことで非同期をうまく制御することができます。

```javascript
new Promise((resolve, reject) => {
  console.log('promise');
  resolve('hello');
})
  .then((data) => {
    console.log(`then: ${data}`);
    return data;
  })
  .then((data) => {
    console.log(`then: ${data}`);
    return data;
  })
  .catch((data) => {
    console.log(`catch: ${data}`);
  })
  .finally((data) => {
    console.log(`finally: ${data}`);
  });

console.log('global');

// コンソール
// 1. promise
// 2. global

// ...1秒後　

// 3. then: hello
// 4. then: hello
// 5. filally: undefined
```

１秒間経った後に`resolve`が呼ばれるので、後続の処理は`resolve`が呼ばれた1秒後に実行されます。
`resolve`を`reject`に変えた場合も同様です。

Promiseはこのようにして非同期処理を実行することができます。