Promiseの使い方、それに代わるasync/awaitの使い方

JavaScriptのasync/awaitはPromiseを使った非同期感たっぷりのコードを同期処理っぽくしてくれるが、async/awaitを理解するには、Promiseも知る必要がある。そこで、Promiseからおさらいしておこうと思う。

## 構文

まず基本的な構文。どうやってPromiseを作成するのか？

```js
function 非同期的な関数(成功時コールバック, 失敗時コールバック) {
  if (...) {
    成功時コールバック(成果)
  } else {
    失敗時コールバック(問題)
  }
}

//          ↓executor
new Promise(function (resolve, reject) {
  非同期的な関数(
    (成果) => resolve(成果), // 成功時コールバック関数
    (問題) => reject(問題),  // 失敗時コールバック関数
  )
})
```

* `Promise`クラスを`new`して使う。
* コンストラクタの引数
    * 引数は`executor`1つだけ
    * `executor`は`Function`型
        * `resolve`は`executor`に渡ってくる関数
            * 非同期な処理が成功したとき、`resolve`に成果となる値を渡す。
        * `reject`は`executor`に渡ってくる関数
            * 非同期な処理が失敗したとき、`reject`に問題となるエラーオブジェクトなどを渡す。

### 例: axiosとPromise

```js
/**
 * サーバの死活監視。ステータスコードを返す。
 * @returns {Promise.<number>}
 */
function getServerStatusCode() {
    return new Promise(function(resolve, reject) {
        axios
            .get("https://httpbin.org/status/200")
            .then(response => resolve(response.status))
            .catch(error => reject(error.response.status))
    });
}

getServerStatusCode()
    .then(statusCode => console.log("生きてる", statusCode))
    .catch(statusCode => console.error("死んでる", statusCode))
```

* CodePen → [Axios and Promise](https://codepen.io/suin/pen/eyGjmq?editors=0010)
* :warning: これはちょっと冗長なコードになっている。もっとすっきり書く方法は後述する。

## Promise.resolve

```js
new Promise(function (resolve, reject) {
    resolve(1)
})
```

このコードは下のコードに書き換えることができる

```js
Promise.resolve(1)
```

なので、`then`などとメソッドチェーンもできる

```js
Promise.resolve(1)
    .then(value => {
        console.log(value)
    })
```


## asyncとawait - 非同期処理を同期処理っぽく書く

ご覧の通り、非同期処理の結果は`then`にコールバックを与えないと処理できない。同期処理のコードと比べると一手間必要だ。

```js
/**
 * @returns {Promise.<number>}
 */
function 非同期処理() {
    return Promise.resolve(1)
}

/**
 * @returns {number}
 */
function 同期処理() {
    return 1
}

function main() {
    console.log(1 + 非同期処理()) // 1[object Promise]
    console.log(1 + 同期処理()) // 2
    // Promiseの結果の処理はthenが必要
    非同期処理().then(value => console.log(1 + value)) // 2
}

main()
```

asyncとawaitを使うと同期処理っぽく書けるようになる。

* `await` → Promiseの値が取り出されるまで待つ。
* `async` → `await`キーワードを使っている関数のあたまに付ける必要がある。

```js
/**
 * @returns {Promise.<number>}
 */
function 非同期処理() {
    return Promise.resolve(1)
}

async function main() {
    console.log(1 + 非同期処理()) // 1[object Promise]
    console.log(1 + await 非同期処理()) // 2
}

main()
```

### 例: axiosとasync/awaitによる同期的な書き方

```js
/**
 * サーバの死活監視。ステータスコードを返す。
 * @returns {Promise.<number>}
 */
async function getServerStatusCode() {
  try {
    return (await axios.get("https://httpbin.org/status/500")).status
  } catch (error) {
    throw error.response.status
  }
}

getServerStatusCode()
    .then(statusCode => console.log("生きてる", statusCode))
    .catch(statusCode => console.error("死んでる", statusCode))
```

* 前述したaxiosとPromiseの例をasync/awaitに一部書き換えたもの。
* CodePen → [Axios and async](https://codepen.io/suin/pen/wprYBg?editors=0012)

## async functionはPromiseを返す

asyncはawaitとセットで使う必要はない。単体の機能としては関数を`Promise`を返す関数に変身させること。

```js
/**
 * @returns {Promise.<number>}
 */
function getNumber1() {
    return Promise.resolve(1)
}

/**
 * @returns {Promise.<number>}
 */
async function getNumber2() {
    return 1
}

console.log(getNumber1()) // Promise { 1 }
console.log(getNumber2()) // Promise { 1 }
```

## async function内で例外を飛ばしたらどうなるか？

async function内で例外をthrowした場合でも、あくまで関数の戻り値は`Promise`型。例外も`.catch()`で処理できる。

```js
/**
 * @returns {Promise.<void>}
 */
async function throwError() {
    throw "err"
}

throwError().catch(console.log) // errと出力される
```

## async function内でPromiseを返したらどうなるか？

戻り値がPromiseなら、そのまま返される。なので、Promiseが二重になることはない。

```js
/**
 * async functionなのにPromiseをreturnする関数
 * @returns {Promise.<number>}
 */
async function promise() {
    return Promise.resolve(1)
}

/**
 * @returns {Promise.<number>}
 */
async function number() {
    return 1
}

promise().then(console.log) // 1
number().then(console.log) // 1
```

これはasync functionの性質というより、Promiseの性質のようだ。`Promise.resolve`をいれこにしても`Promise`が多重にならない。

```js
console.log(
    Promise.resolve(
        Promise.resolve(
            Promise.resolve(1)
        )
    )
) //=> Promise { 1 }
```

## finallyはPromiseにもある？

async/awaitだとfinallyで成功時・失敗時どちらでも共通の処理を実行できる。

```js
/**
 * @returns {Promise.<void>}
 */
async function getStatusCode() {
    try {
        console.log('started')
        const response = await axios.get('https://httpbin.org/status/200')
        console.log('success', response.status)
    } catch (error) {
        console.log('error', error.response.status)
    } finally {
        console.log('finished') // 共通処理
    }
}
```

これと同じことはPromiseでもできるのだろうか？

```js
function getStatusCode() {
    console.log('started')
    return axios.get('https://httpbin.org/status/200')
        .then(response => {
            console.log('success', response.status)
        })
        .catch(error => {
            console.log('error', error.response.status)
        })
        .finally(_ => {
            console.log('finished') // !?!?
        })
```

結論を言うと、無いことはないが[Promise.prototype.finally() - JavaScript | MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally)に書いてある通り、2019-05-28現時点でChromeやEdgeをはじめとした多くのブラウザが対応しているが、Internet Explorerは未対応である。なお、babel-polyfilが入っていれば、finallyも使えるようだ。


## Promiseでもそんなにコードの保守性悪くないのでは？

ここまで見てきたサンプルコードでは、Promiseを使っていてもそこまで保守性を損ねそうにない。では、どういうときにasync & awaitのほうがコードがシンプルになるのだろうか。それは、**2段以上Promiseのthenが重なるとき**だと思う。例えば次のようなコード。

```js
function main() {
    getX().then(x => {
        getY().then(y => {
            console.log(x + y)
        })
    })
}

function getX() {
    return Promise.resolve(1)
}

function getY() {
    return Promise.resolve(2)
}

main()
```

これをasync & awaitに書き換えるとすっきりする。

```js
async function main() {
    const x = await getX()
    const y = await getY()
    console.log(x + y)
}

async function getX() {
    return 1
}

async function getY() {
    return 2
}

main()
```

## 今日現在のPromiseにはmapが生えていない

* Promiseにはmapメソッドが生えていない。
* サードパーティでそれを可能にするライブラリがあるかも？ 探してない。


## 参考文献

* [Promise - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise)

---

最後までお読みくださりありがとうございました。Twitterでは、Qiitaに書かない技術ネタなどもツイートしているので、よかったらフォローお願いします:relieved:→[Twitter@suin](https://twitter.com/suin)