axios を async/await で書き直した時の課題をメモ

## 概要
request が deprecated になっており、トレンドも axios に移っているようだ。

* [axios vs request | npm trends](https://www.npmtrends.com/axios-vs-request)

なので、コードの置き換えを検討している。async/await にすると可読性があがるかもと試した内容を記載する。

## 結論

結論からいうと因果関係のある処理を async/await に置き換えると可読性が高まる。

## サンプルコード: then()を使う

ユーザー認証をして得たトークンを元にリクエストをする例。
簡単すぎるコードでは検証にならないのでやや複雑なコードになっている。

```javascript
'use strict'

const axios = require('axios');

axios(
  {
    url: 'https://httpbin.org/post?token=XXXXXXXX',
//  url: 'https://httpbin.org/status/500?token=XXXXXXXX',
    headers: {
      'Content-Type': 'application/json'
    },
    method: 'post',
    data: {
      id: 'john.doe',
      pass: '********'
    }
  }
).then(function (responseToken) {
  console.log(`Request URI: ${responseToken.config.url}`);
  const token = responseToken.data.args.token;
  console.log(token);
  axios(
    {
      url: 'https://httpbin.org/get?foo=bar',
      headers: {
        'Authorization': 'Bearer ' + token
     },
     method: 'get'
    }
  ).then(function (response) {
    console.log(`Request URI: ${response.config.url}`);
    console.log(`response body: ${JSON.stringify(response.data.args)}`);
  }).catch(function(error) {
    console.log(`Request URI: ${error.config.url}`);
    console.log(`${error.response.status} ${error.response.statusText}`);
    return `${error.response.status} ${error.response.statusText}`
  })
}).catch(function(error) {
  console.log(`Request URI: ${error.config.url}`);
  console.log(`${error.response.status} ${error.response.statusText}`);
  return `${error.response.status} ${error.response.statusText}`
})
```

**正常時の実行結果**

```
Request URI: https://httpbin.org/post?token=XXXXXXXX
XXXXXXXX
Request URI: https://httpbin.org/get?foo=bar
response body: {"foo":"bar"}
```

**異常時の実行結果**
入れ子なので token 取得に失敗したら、 `/get?foo=bar` の処理は実行はしない。期待通りの動作。

```
Request URI: https://httpbin.org/status/500?token=XXXXXXXX
500 INTERNAL SERVER ERROR
```

## サンプルコード: await/asyncを使う

ちょっとわかりにくいと思うが、`await axios()` `await axios()` と同じレベルでコードが並んでいる。
入れ子でないので、前のリクエストの結果に依存する処理が増えても可読性が悪くはならない。

例外は二つのリクエストで共通となる。`let token = undefined` というように `let` を使っているのは、`catch` ブロック中でどこまで進んだのか判断できるようにするため。これで、処理の進み具合によってエラー処理を変えることができるため、前述の then/catch のように細かいエラー処理も可能になる。

```javascript
'use strict'

const axios = require('axios');

async function main() {
  let token = undefined
  let resOfGetToken = undefined
  let resOfGetFoo = undefined
  try {
    resOfGetToken = await axios(
      {
        url: 'https://httpbin.org/post?token=XXXXXXXX',
        // url: 'https://httpbin.org/status/500?token=XXXXXXXX',
        headers: {
          'Content-Type': 'application/json'
        },
        method: 'post',
        data: {
          id: 'john.doe',
          pass: '********'
        }
      }
    )
    token = resOfGetToken.data.args.token;
    console.log(`Request URI: ${resOfGetToken.config.url}`);
    console.log(`token: ${token}`);
 
    resOfGetFoo = await axios(
      {
        url: 'https://httpbin.org/get?foo=bar',
        headers: {
          'Authorization': 'Bearer ' + token
       },
       method: 'get'
      }
    )    
    console.log(`Request URI: ${resOfGetFoo.config.url}`);
    console.log(`response body: ${JSON.stringify(resOfGetFoo.data.args)}`);

  } catch(error) {
    console.log("error.name=" + error.name);
    console.log("error.toString()=" + error.toString());
    console.log("Error.prototype.toString()=" + Error.prototype.toString())
    if ('isAxiosError' in error) {
      console.log(`Request URI: ${error.request.path}`);
      console.log(`${error.response.status} ${error.response.statusText}`);
    }
  }
}

try {
  main()
} catch(e) {
  console.log(e)
}
```

**正常時の実行結果**

```
Request URI: https://httpbin.org/post?token=XXXXXXXX
token: XXXXXXXX
Request URI: https://httpbin.org/get?foo=bar
response body: {"foo":"bar"}
```

**異常時の実行結果**
token 取得に失敗し、後続の処理を中断できている。

```
error.name=Error
error.toString()=Error: Request failed with status code 500
Error.prototype.toString()=Error
Request URI: /status/500?token=XXXXXXXX
500 INTERNAL SERVER ERROR
```

## サンプルコード3: パラメータを組み立てる処理を関数化した

```javascript
'use strict'

const axiosBase = require('axios');
const axiosToBFF = axiosBase.create(
  {
    baseURL: 'https://httpbin.org'
  }
)

async function getToken(id_, pass_) {
  return axiosToBFF.request(
    {
      url: '/post?token=XXXXXXXX',
      // url: '/status/500?token=XXXXXXXX',
      headers: {
        'Content-Type': 'application/json'
      },
      method: 'post',
      data: {
        id: id_,
        pass: pass_
      }
    }
  )
}

async function getFoo(val) {
  return axiosToBFF.request(
    {
      url: `/get?foo=${val}`,
      headers: {
        'Accept': 'application/json',
        'Cache-Control': 'no-cache'
      },
      method: 'get'
    }
  )
}

async function main() {
  let accessToken = undefined
  let resOfGetToken = undefined
  let resOfGetFoo = undefined
  try {
    resOfGetToken = await getToken('john.doe', '********')
    accessToken = resOfGetToken.data.args.token;
    console.log(`Request URI: ${resOfGetToken.config.url}`);
    console.log(`token: ${accessToken}`);

    axiosToBFF.defaults.headers.common['Authorization'] = `Bearer ${accessToken}`

    resOfGetFoo = await getFoo("bar") 
    console.log(`Request URI: ${resOfGetFoo.config.url}`);
    console.log(`response body: ${JSON.stringify(resOfGetFoo.data.args)}`);
  } catch(error) {
    console.log("error.name=" + error.name);
    console.log("error.toString()=" + error.toString());
    console.log("Error.prototype.toString()=" + Error.prototype.toString())
    if ('isAxiosError' in error) {
      console.log(`Request URI: ${error.request.path}`);
      console.log(`${error.response.status} ${error.response.statusText}`);
    }
  }
}

try {
  main()
} catch(e) {
  console.log(e)
}
```

## 参考
* [APIクライアント開発時のモックに使えるhttpbinの紹介 - Qiita](https://qiita.com/sameyasu/items/adacceb8a1bee893599b)
* [Promiseの使い方、それに代わるasync/awaitの使い方 - Qiita](https://qiita.com/suin/items/97041d3e0691c12f4974)
* [javascript - Can I throw error in axios post based on response status - Stack Overflow](https://stackoverflow.com/questions/43842711/can-i-throw-error-in-axios-post-based-on-response-status)