【ES6】 JavaScript初心者でもわかるPromise講座

## はじめに
`Promise`って...難しくないですか？？？
3ヶ月くらい前の私は、`Promise`をほとんど理解できてないのに`async/await`とか使っちゃってたし、様々な記事を読み漁ってもなかなか理解できず、`Promise`の正体を掴むのに時間がかかってしまいました。

そんな3ヶ月くらい前の自分にも伝わるように、できる限り丁寧に`Promise`を説明していこうと思います。

### 前提
本記事では、Promise以外のES6の書き方に関しては触れておりません。
アロー関数やテンプレート文字列などを例文で用いているため、わからない方がいましたら下記記事などを参考にしていただけると幸いです。
> [ES2015(ES6) 入門](https://qiita.com/soarflat/items/b251caf9cb59b72beb9b)

## `Promise`とは
### JavaScriptは処理を待てない！
まずはじめに、下記コードを実行してみると、どのような結果になるでしょうか。

```js
console.log("1番目");

// 1秒後に実行する処理
setTimeout(() => {
  console.log("2番目(1秒後に実行)");
}, 1000);

console.log("3番目");
```

`1番目`、`2番目`、`3番目`...という順番で実行されることが理想的ではありますね。

しかし、実際には下記のような順序で実行されてしまいます。

```console
1番目
3番目
2番目(1秒後に実行)
```
JavaScriptは**非同期言語**であるため、**一つ前の実行に時間がかかった場合、実行完了をまたずに次の処理が行われてしまいます**。

### `Promise`は、処理の順序に「`お約束`」を。
`Promise`を日本語に翻訳すると「**約束**」です。
なので、私は`Promise`のことを、処理の順序に「**お約束**」を取り付けることができるもの、**処理を待機することや、その結果に応じて次の処理をすることお約束するもの**だと思っています。

先ほどの例で詳しく見ていきましょう。
(コードの中身は、後ほど説明していきますので、わからなくて大丈夫です！)

```js
console.log("1番目");

// お約束を取り付けたい処理にPromise
new Promise((resolve) => {
  //1秒後に実行する処理
  setTimeout(() => {
    console.log("2番目(1秒後に実行)");
    //無事処理が終わったことを伝える
    resolve();
  }, 1000);
}).then(() => {
  // 処理が無事終わったことを受けとって実行される処理
  console.log("3番目");
});
```

すると、実行結果は下記のようになります。

```
1番目
2番目(1秒後に実行)
3番目
```

`Promise`を用いることで、理想の処理を実行することができました。

それでは、より詳しく`Promise`をみていきましょう。

### `Promise`には3つの状態がある
`Promise`には、`PromiseStatus`というstatusがあり、3つのstatusがあります。

* `pending`: **未解決** (処理が終わるのを待っている状態)
* `resolved`: **解決済み** (処理が終わり、無事成功した状態)
* `rejected`: **拒否** (処理が失敗に終わってしまった状態)

`new Promise()`で作られたPromiseオブジェクトは、`pendeing`という`PromiseStatus`で作られます。
処理が成功した時に、`PromiseStatus`は`resolved`に変わり,`then`に書かれた処理が実行されます。
処理が失敗した時は、`PromiseStatus`が`rejected`に変わり、`catch`に書かれた処理が実行されます。

<img width=500px src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142322/640d97d0-e8f9-9a56-6673-9a551d70cea3.png'>

`Promise`の大まかな処理の流れはわかりましたでしょうか。
それでは実際に書いてみましょう...！

## `Promise`の書き方

### Promiseインスタンスの作成

```sample1.js
const promise = new Promise((resolve, reject) => {});
```

Promiseの引数には関数を渡し、その関数の第一引数に`resolve`を設定し、第二引数に`reject`を任意で設定します。
(`resolve`も`reject`も関数です。)

ちなみに、上記コードにて定義した`promise`を`console.log`すると下記のような`Promiseオブジェクト`が返ってきます。

<img width="300" alt="Promiseオブジェクト pending" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142322/c59d8964-471c-737b-e9cd-45d9ed960a4e.png">

作りたてのPromiseオブジェクトなので、`PromiseStatus`は、`pendeing`ですね。

### `resolve`させよう
`sample1`のコードを`resolve`させてみましょう。

```js
// rejectは今回使わないため、引数から削除
const promise = new Promise((resolve) => {
  resolve();
}).then(() => {
  console.log("resolveしたよ");
});
```

実行結果は下記のようになります。

```
resolveしたよ
```

`Promiseオブジェクト`を確認してみると...
<img width="428" alt="Promiseオブジェクト resolved" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142322/27f0be55-c490-4616-de8b-d4c6a6f55094.png">

`resolve()`を呼び出すことによって、
`PromiseStatus`は`resolved`に変わり,`then`の処理が走ってることがわかりますね。

#### `resolve`関数に引数を渡してあげると...
`resolve`関数は、引数を受け取ることができ、次に呼ばれるメソッド(then)の第一引数に渡してあげることができます。

```js
const promise = new Promise((resolve) => {
  // 引数に文字列を渡す
  resolve("resolveしたよ");
}).then((val) => {
  // 第一引数にて、resolve関数で渡した文字列を受け取ることができる
  console.log(val);
});
```
```実行結果.
resolveしたよ
```

resolve関数の引数にて渡された値が、`then`の第一引数に渡されてることがわかりますね。

下記コード時点でのpromiseを確認してみましょう。

```js
const promise = new Promise((resolve) => {
  // 引数に文字列を渡す
  resolve("resolveしたよ");
});
```

<img width="365" alt="スクリーンショット 2020-03-22 18.24.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142322/d2f6d781-b2d2-c974-799f-6b054817e890.png">

`PromiseValue`に、resolveに渡した文字列が入ってることがわかりましたね。
`PromiseValue`に格納された値を、次に呼ばれるメソッドの第一引数に渡すことができる、という仕組みになっております。

### `reject`させよう
`sample1`のコードを`reject`させてみましょう。

```js
const promise = new Promise((resolve, reject) => {
  reject();
})
  .then(() => {
    console.log("resolveしたよ");
  })
  .catch(() => {
    console.log("rejectしたよ");
  });
```
実行結果は下記のようになります。

```
rejectしたよ
```

`then`の処理は実行されず、`catch`の処理が実行されることがわかりますね。

`Promiseオブジェクト`を確認してみると...

<img width="290" alt="スクリーンショット 2020-03-22 18.42.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142322/8acadf54-b065-ac9b-cc07-16c19b1f736c.png">

想定通り、`PromiseStatus`が`rejected`に.....なってない！！！！！

### なぜ`rejected`にならない...？
下記コード、つまり`catch`の処理を行なっていない状態の`Promiseオブジェクト`をみてみましょう。

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

<img width="351" alt="Promiseオブジェクト reject" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142322/ca8ec242-e7f2-9bfa-eef4-0e7c71738769.png">

この時点では、`PromiseStatus`が`rejected`ですね。
ちょっと安心ですね。笑

(`Uncaught (in promise)`というエラーはrejectされた時に、一回も`catch`の処理が行われなかった時にでます。)

実は、`catch`にて実行される関数がreturnした値を`resolve`します。
めちゃくちゃ簡単にいうと、`catch`はエラー返したら満足して、解決済みだ！ってするみたいです。
つまり、catchにて返された`promiseオブジェクト`の`PromiseStatus`は`resolve`になります。

んー、難しい。

### Promiseのメソッドチェーン
処理が成功した時に`then`に書かれた処理が実行され、処理が失敗した時は`catch`に書かれた処理が実行されると説明しましたが、それらの処理の後にまた別の`then`を実行することができます。

さらに、returnで返した値を第一引数として次の`then`に渡すことが可能です。


```resolveした場合.js
const promise = new Promise((resolve, reject) => {
  resolve("test");
})
  .then((val) => {
    console.log(`then1: ${val}`);
    return val;
  })
  .catch((val) => {
    console.log(`catch: ${val}`);
    return val;
  })
  .then((val) => {
    console.log(`then2: ${val}`);
  });
```
```実行結果.
then1: test
then2: test
```

`resolve`されたため、1つめの`then`が実行されたあと、
`return`で`val`を受け取り、2つめの`then`が実行されました。

```rejectした場合.js
const promise = new Promise((resolve, reject) => {
  reject("test");
})
  .then((val) => {
    console.log(`then1: ${val}`);
    return val;
  })
  .catch((val) => {
    console.log(`catch: ${val}`);
    return val;
  })
  .then((val) => {
    console.log(`then2: ${val}`);
  });
```
```実行結果.
catch: test
then2: test
```

`reject`されたため、1つめの`then`は処理されず、
1つめの`catch`が実行されたあとに2つめの`then`が実行されました。

## `Promise.all`と`Promise.race`
これらは、`Promise`を複数実行して、その結果に応じて次の処理に進む便利メソッドです。

### `Promise.all`
`Promise.all()`は配列でPromiseオブジェクトを渡し、
全てのPromiseオブジェクトが`resolved`になったら次の処理に進みます。

```js
const promise1 = new Promise((resolve) => {
  setTimeout(() => {
    resolve();
  }, 1000);
}).then(() => {
  console.log("promise1おわったよ！");
});

const promise2 = new Promise((resolve) => {
  setTimeout(() => {
    resolve();
  }, 3000);
}).then(() => {
  console.log("promise2おわったよ！");
});

Promise.all([promise1, promise2]).then(() => {
  console.log("全部おわったよ！");
});
```

```実行結果.
promise1おわったよ！
promise2おわったよ！
全部おわったよ！
```

### `Promise.race`
`Promise.race()`は`Promise.all()`と同じく配列でPromiseオブジェクトを渡し、
どれか1つのPromiseオブジェクトが`resolved`になったら次に進みます。

```js
const promise1 = new Promise((resolve) => {
  setTimeout(() => {
    resolve();
  }, 1000);
}).then(() => {
  console.log("promise1おわったよ！");
});

const promise2 = new Promise((resolve) => {
  setTimeout(() => {
    resolve();
  }, 3000);
}).then(() => {
  console.log("promise2おわったよ！");
});

Promise.race([promise1, promise2]).then(() => {
  console.log("どれか一つおわったよ！");
});
```

```実行結果.
promise1おわったよ！
どれか一つおわったよ！
promise2おわったよ！
```

## `async/await`を使ってもっと簡潔に！
`Promise`の処理は、`Promiseインスタンス`を生成したり,`resolve`/`reject`したりするため、場合によっては結構冗長的になってしまいます。
`async/await`を使うと`Promise`の処理をより簡潔に書くことができます。

例に、下記の`Promise`の処理を`async/await`を使って、リファクタします。

```js
const alwaysLateBoy = (ms) => {
  new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, ms);
  }).then(() => {
    console.log(`すまん！${ms}ms待たせたな。`);
  });
};

```

処理としては、引数で渡した`ms`秒分待機して、その後`console.log`を走らせれば良さそうですね。

上記の処理を`async/await`を用いると、下記のようにまとめることができます。

```js
// async function を定義
const alwaysLateBoy = async (ms) => {
  await new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, ms);
  })
  // Promiseの結果が返ってくるまで実行されない
  console.log(`すまん！${ms}ms待たせたな。`)
};
```

### `async`とは
`async`は非同期関数を定義する関数宣言であり、関数の頭につけることで、
`Promiseオブジェクト`を返す関数にすることができます。
そのような関数を`async function`といいます。

```js
const asyncFunc = async () => {
  return 1;
};
```

上記`async function`を実行すると、下記のような`Promiseオブジェクト`が返ってきます。

<img width="300" alt="スクリーンショット 2020-03-22 22.00.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142322/8b5277ec-ecb6-90e4-f7ec-d8327a8310eb.png">

`async function`が値をreturnした場合、Promiseは戻り値をresolveし、
その値は`PromiseValue`として扱われます。

### `await`とは
`await`は、`Promiseオブジェクト`が値を返すのを待つ演算子です。
`await`は**必ず、`async function`内で使います**。

下記のコードを例に考えてみましょう。

```js
const asyncFunc = async () => {
  let x, y;
  x = new Promise(resolve => {
    setTimeout(() => {
      resolve(1);
    }, 1000 )
  })
  y = new Promise(resolve => {
    setTimeout(() => {
      resolve(1);
    }, 1000 )
  })
  console.log(x + y)
}
```

この処理では、Promiseが`resolve`するのを待つ処理を書いてないため、
`console.log(x + y)`では、下記のような結果が返ってきます。

```実行結果...
NaN
```

この処理を、Promiseが`resolve`するのを待つようにするには、
この`Promise`オブジェクトが全て実行完了するのを待つ`Promise.all`を使えばできそうですが、`await`を使うともっと簡単に実装できます。

```js
const asyncFunc = async () => {
  let x, y;
  // Promiseがresolveするまで待機
  x = await new Promise((resolve) => {
    setTimeout(() => {
      resolve(1);
    }, 1000);
  });
  // Promiseがresolveするまで待機
  y = await new Promise((resolve) => {
    setTimeout(() => {
      resolve(1);
    }, 1000);
  });

  console.log(x + y);
};
```

`console.log(x + y)`は、上二つのPromiseが値を返すのを待ってから実行されるため、実行結果は下記のようになります。

```実行結果...
2
```

## おわりに
想像以上に長くなってしまい、後半からわかりづらいかもしれません。。。泣
少しでも`Promise`に対して苦手意識がなくなったら良いなと思います。
(自分自身、この記事を書いていくうちに、さらにPromiseを理解できたような気がします。)

## 参考記事
* [Promiseによる非同期処理の書き方](https://numb86-tech.hatenablog.com/entry/2017/01/02/152517)
* [【JavaScript】非同期処理/Promise/asyncと聞くとビビっちゃう人を救う](https://qiita.com/miyarappo/items/dbe1a9bad36c5e35b623#promise%E3%81%A8%E3%81%AF%E4%BD%95%E3%81%8B)
* [Promiseの使い方、それに代わるasync/awaitの使い方](https://qiita.com/suin/items/97041d3e0691c12f4974)
* [async/await 入門（JavaScript）](https://qiita.com/soarflat/items/1a9613e023200bbebcb3)