Promise と async/await の理解度をもう1段階上げる


## はじめに

表題のモチベーションで書き上げた備忘録に加筆・修正したものを記事にしました。
記事を読んで下さった方の Promise, async/await の理解度が 1LV でもアップしてくれたら嬉しく思います。

## Promise とは

- Promise は非同期関数を扱うためのインターフェース
- Promise は **悲運のピラミッド型コールバック** （ネスト地獄）を根本的に解決してくれる
- thenable と呼ばれる then メソッドを有するオブジェクトを resolve の第一引数に入れることで Promise オブジェクトへ変換することが可能

```javascript
// thenable を Promise オブジェクトへ変換する
const converted = Promise.resolve({
  then: (onFulfilled) => onFulfilled('be promise')
})
converted.then(value => console.log(value)) // be promise
```

## async/await とは

- async/await は Promise のシンタックスシュガーとして用いられる
- async/await は Promise を同期的なコードのように書ける


## Promise の書き方とメソッドの簡単な説明

### Promise の最も基本的なコード

```javascript
const promise = new Promise((resolve, reject) => resolve('解決した'))
const unResolvedPromise = new Promise((resolve, reject) => reject('エラー発生'))

promise
  .then((value) => value) // 解決した

unResolvedPromise
  .then((value) => value) 
  .catch((errorMessage) => new Error(errorMessage)) // error: エラー発生
  .then((value) => value) // エラー発生
```

### then と catch

then と catch はプロミスにおける最重要事項の一つです。これらは promise を返すため、メソッドチェーンを繋げることができます。

#### then
`Promise#then(onFulfilled, onRejected) `

- Promise に成功ハンドラ(onFulfilled) と失敗ハンドラ(onRejected) を付加する
- 2つの引数を持ち、それらはそれぞれ Promise の状態が Fulfilled の時と Rejected の時のコールバック関数である
- 返り値は promise

**onFulfilled(value)**

- 一つの引数 value を持つ
- value は promise にラップされた値など

**onRejected(reason)**

- 一つの引数 reason を持つ
- reason は rejected となった理由など

#### catch
`Promise#catch(onRejected)`

- `then(undefined, onRejected)` のショートカットとして機能する
- 返り値は promise
- 引数の onRejected は then と参照


### 静的メソッド

#### Promise.resolve
下記コードのエイリアスです。渡した値で Fulfilled される promise オブジェクトを返します。

```javascript
new Promise((resolve) => resolve(/* value */))
```

#### Promise.reject
下記コードのエイリアスです。

```javascript
new Promise((resolve, reject) => reject(/* reason */))
```

#### Promise.all
- 引数に Promise 関数の配列を取る実行
- 返り値は Promise
- 引数に取った Promise で一つでもエラーが発生した場合はその rejected されたエラーが返る
- エラーが発生しなかった場合は「value にそれぞれの resolve の結果が入った配列」の Promise が返る

```javascript
Promise.all(promises).then(allFulfilled, firstRejected)
```

#### Promise.race
- 引数に Promise 関数の配列を取り実行する
- 引数にとった Promise 関数のいずれかが fullFilled または rejected になった時点でその Promise を返す

```javascript
Promise.race(promises).then(firstFulfilled, firstRejected)
```

## Promise の仕様的なところ

async/await をちゃんと使えるようになるには Promise が内部でどう働いているかある程度わかっている必要があります。

### Promise は状態をもつ
インスタンス化された promise のオブジェクトには以下の 3 つの状態が存在します。

1. Fulfilled
2. Rejected
3. Pending

- promise のオブジェクトの状態は不変である
- 一度 Fulfilled か Rejected になったら Pending には戻らない
- Fulfilled, Rejected は Settled（落ち着いた状態, 不変の状態）ともいう
- Pending から Settled になったら状態はもとに戻らない

### 渡された関数の実行タイミング
- すぐに実行されるのではなく、マイクロタスクのキューに入れられる
- このキューは現在のイベントループの終わりに空になる

## async/await の書き方

`console.log(101)` するコードを (1)then catch, (2)async/await の2パターン書きました。
`doSomething` 関数内でエラーが発生した場合は `failureCallback` にエラーが渡されてログに出すようにしています。

### then と catch を使った場合

```javascript
const doSomething = () => Promise.resolve(100)
const failureCallback = (e) => console.log(e)

doSomething()
  .then(v => v + 1)
  .then(v => console.log(v)) // 101
  .catch(failureCallback);
  
```

### async/await を使った場合
```javascript

// ※ 即時関数で囲っています
const doSomething = () => Promise.resolve(100)
const failureCallback = (e) => console.log(e)

(async () => {
  try {
    let result = await doSomething()
    result = result + 1
    console.log(result) // 101
  } catch (e) {
    failureCallback(e)
  }
})();
```

## サンプルコード集

筆者の場合 Promise, async/await は説明やコードを読んでいるだけではいまいち理解できなかったので、実際にコードを書いて動かして理解を深めました。その過程で特に残しておきたい（忘れた時に後から見たい）と思ったコードを載せております。

### 古いコールバックを使った API をラップする

setTimeout をラップします。これはコールバックのAPIをラップしたもっともシンプルな例かもしれません。

```javascript
// setTimeout を Promise にする
const wait = ms => {
  return new Promise((resolve) => setTimeout(resolve, ms))
}
wait(1000).then(() => console.log('logged after 1000ms'))
```

### 複数の関数を直列に合成する

```javascript
const composeAsync = (...funcs) => {
  return x => {
    return funcs.reduce((acc, val) => {
      return acc.then(val)
    }, Promise.resolve(x))
  }
}

// 使用例
const succ = n => n + 1
const multiply = x => y => x * y

const transformData = composeAsync(succ, succ, multiply(3))
transformData(2)
  .then(v => console.log(v)) // 12
```

### async/await と Promise のコードを比較してみる

async function 内で `console.log(100)`、async function を呼ぶときに `console.log(100)` するコードで比較してみます。両コードの挙動は全く同じです。
`async/await` 使った方が圧倒的に書きやすく、またコードを見た時にどういう動きをしそうか、直感的に理解しやすいと思いました。

#### async/await
```javascript
const ret100 = () => Promise.resolve(100)

const asyncFunc = async () => {
  let result = await ret100()
  result += await ret100()
  console.log(result) // log.1 200
  return result // Promise を返す
}
asyncFunc()
    .then(value => console.log(value)) // log2. 200
```

#### Promise

```javascript
const ret100 = () => Promise.resolve(100)

const asyncFuncPromise = () => {
  let result = 0
  return ret100()
    .then(value => {
      result += value
      return ret100()
    })
    .then(value => {
      result += value
      console.log(result) // log.1 200
      return result
    })
}

asyncFuncPromise()
  .then(value => console.log(value)) // log.2 200
```

### async/await のエラー処理の記述パターン

下記はサンプルコード内で使用している errorPromise 関数です。

```javascript
const errorPromise = () => {
  return new Promise((resolve, reject) => {
    try {
      throw new Error('Some error occured')
      resolve('no error')
    } catch (err) {
      reject(err)
    }
  })
}
```

#### 1. try/catch を使う
```javascript
const asyncFuncTryCatch = async () => {
  try {
    const result = await errorPromise()
    console.log(result) // unreachable
  } catch (err) {
    console.log(err)
  }
}

asyncFuncTryCatch() // log. Some error occured
```

#### 2. try/catch を使わない
```javascript
const asyncFuncNoTryCatch = async () => {
  const result = await errorPromise()
    .catch(err => err)
  console.log(result)
}

asyncFuncNoTryCatch() // Some error occured
```

#### 3. async function 内でエラーハンドリングしない
```javascript
const asyncFuncHandleErrorOut = async () => {
  const result = await errorPromise()
  console.log(result) // unreachable
}

// async function を呼ぶ時にエラーハンドリング
asyncFuncHandleErrorOut()
  .catch(err => console.log(err)) // Some error occured

```

#### おまけ. Promise のエラーハンドリング
```javascript
const asyncPromiseFunc = () => {
  return errorPromise()
    .then(value => value)
    .catch(err => err)
}

asyncPromiseFunc() // Some error occured
```

### 非同期関数を並列で実行させたいとき

非同期関数を並列で実行させたいときは `Promise.all` を使用する必要があります。

例えば、下記のようなコードがあり、 `getFood` と `getDrink` を並列で実行し、両関数が完了した後に `orderItems` を実行したいとします。

```javascript


const getFood = () => {
  return new Promise(resolve => {
    setTimeout(resolve, 1000, 'got food')
  })
}

const getDrink = () => {
  return new Promise(resolve => {
    setTimeout(resolve, 1000, 'got a drink')
  })
}

const orderItems = (...items) => {
  return new Promise(resolve => {
    setTimeout(() => {
      items.forEach((msg) => console.log(msg))
    }, 2000)
  })
}

```

#### NGパターン

下記コードは `getFood` > `getDrink` > `orderItems` のように直列で実行されてしまいます。

```javascript
const myOrder = async () => {
  const msg1 = await getFood()
  const msg2 = await getDrink()
  orderItems(msg1, msg2)
}

myOrder()

```

#### OKパターン(1)

~~(`getFood`, `getDrink`) > `orderItems` のように並列で実行します。~~
~~NGパターン(1) 同様 `getFood` > `getDrink` > `orderItems` のように直列で実行されます。~~
（@azu さん 指摘して頂きありがとうございます。）

※修正:20180501.1745
改めて確認したところ、(`getFood`, `getDrink`) > `orderItems` のように並列で実行されるようでした。
但し、 OKパターン(2) のように Promise.all を使用するのが一般的のようなので、そちらを使うのがベターと思われます。

 ```javascript
 const myOrder = async () => {
  const msg1 = getFood()
  const msg2 = getDrink()
  orderItems(await msg1, await msg2)
}

myOrder()

 ```

#### OKパターン(2)

(`getFood`, `getDrink`) > `orderItems` のように `getFood` と `getDrink` は並列で実行されます。一般的に使われているのはこちらのパターンのようです。

 ```javascript
 const myOrder = async () => {
  const msg1 = getFood()
  const msg2 = getDrink()
  const msgs = await Promise.all([msg1, msg2])
  orderItems(...msgs)
}

myOrder()
 
 ```

## おわりに

以上です。
async/await にしろ Promise にしろ、ドキュメントや記事（この記事のような）を読むだけでなく、自分で手を動かしトライ＆エラーを繰り返すことって大事だなとあらためて思いました（どの技術の習得もそうですが・・）。

コードや文章の指摘、質問も頂けると助かります。
最後までお読み頂きありがとうございました。


## 参考資料
- [Promise の本 - azu](http://azu.github.io/promises-book/)
- [Promise/A+ チュートリアル和訳](https://www.yoheim.net/blog.php?q=20140310)
- [Promise を使う - JavaScript | Mozilla](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Using_promises)
- [async function - JavaScript | Mozilla](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/async_function)
- [await - JavaScript | Mozilla](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/await)
- [async/await 入門 - Qiita](https://qiita.com/soarflat/items/1a9613e023200bbebcb3)
- [async/await 地獄 - Qiita](https://qiita.com/rana_kualu/items/e6c5c0e4f60b0d18799d)
- [[フロントエンド] ES7のasync/awaitを使って、Promiseを同期的に処理する - YoheiM.NET](https://www.yoheim.net/blog.php?q=20171008)

## Next steps
- [Promise を実装してみる - POSTD](https://postd.cc/how-do-promises-work/)