# JavaScript axiosをasync、awaitとtry、catch、finallyで制御する

HTTP通信ライブラリのaxios (opens new window)を使って、 APIをasyncawaitで制御するコードを書いていく。

# webpackでbabel7 + webpack-dev-server + json-serverの環境構築

Promiseasyncawaitを使うためにBabel7が使える環境をwebpackで構築していく。ローカルサーバーはwebpack-dev-serverを使う。また、APIのモックサーバーのためにjson-serverを使う。

Babel7の設定ファイルはbabel.config.jsを用意し、
webpackの設定ファイルはwebpack.config.jsを用意する。
json-serverが返すモックのJSONはdb.jsonに用意する。

ディレクトリ

.
├── babel.config.js
├── db.json
├── dist
│   └── index.html
├── package-lock.json
├── package.json
├── src
│   └── index.js
└── webpack.config.js

npm-scriptsでwebpack-dev-serverjson-serverが並列で起動するようにしておく。
json-server--watchでモックのJSONを指定する。--portを指定しておくことで明示的にwebpack-dev-serverとポートが被らないようにする。

package.json

{
  "scripts": {
    "dev": "run-p build mock",
    "build": "webpack-dev-server",
    "mock": "json-server --watch db.json --port 3001"
  },
  "devDependencies": {
    "@babel/core": "^7.4.0",
    "@babel/polyfill": "^7.4.0",
    "@babel/preset-env": "^7.4.2",
    "babel-loader": "^8.0.5",
    "json-server": "^0.14.2",
    "npm-run-all": "^4.1.5",
    "webpack": "^4.29.6",
    "webpack-cli": "^3.3.0",
    "webpack-dev-server": "^3.2.1"
  }
}

babel.config.js

module.exports = function (api) {
  api.cache(true);
  const presets = [
    ['@babel/preset-env', {
      "useBuiltIns": "usage",
    }],
  ];
  return {
    presets,
  };
}

webpack.config.js

const path = require("path");

module.exports = {
  mode: 'development',

  entry: './src/index.js',
  output: {
    path: path.join(__dirname, "dist"),
    filename: 'bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            presets: ['@babel/preset-env']
          }
        }
      },
    ]
  },

  devtool: 'inline-source-map',
  devServer: {
    open: true,
    openPage: "index.html",
    contentBase: path.join(__dirname, 'dist'),
    watchContentBase: true,
    port: 3000,
  }
};

index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Page Title</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>

bundle.js

import '@babel/polyfill';

console.info('hello')

db.jsonのJSONにしたがって、json-serverは自動で以下のAPIを用意する

/posts/
/posts/[id]

db.json

{
  "posts": [
    {
      "id": 1,
      "title": "json-server",
      "author": "typicode"
    }
  ]
}

以上の設定をしたらnpmコマンドでサーバーの起動確認をおこなう。

npm run dev

ブラウザでを開き3000ポートでindex.htmlにアクセスできることを確認する。   webpack-dev-serverの起動確認

また、3001ポートで/postsにアクセスするとJSONが返ってきている。
json-serverの起動確認

これで環境構築の確認ができた。

# axiosをPromiseのthen、catch、finallyで制御

axiosをインストールし、PromiseでHTTPリクエストが成功する場合と、失敗する場合を確認していく。

npm install axios --save

package.json抜粋

  "dependencies": {
    "axios": "^0.18.0"
  }

# HTTPリクエストが成功する場合

axiosでAPIに対してGETする。
/postsへのリクエストは成功するので、catchのコードは実行されず、thenfinallyのコードが実行される。

index.js

import '@babel/polyfill';
import axios from 'axios';

console.info('ローディング表示開始')
axios
  .get('http://localhost:3001/posts')
  .then((response)=> {
    console.info('成功です',response.data);
  })
  .catch((error) => {
    console.error('失敗です', error);
  })
  .finally(() => {
    console.info('ローディング表示終了');
  });

ブラウザのコンソールを確認する。
axios呼び出し前に「ローディング表示開始」」と出力され、APIを呼び出してJSONが取得できており、最後に「ローディング表示終了」が出力されている。

axiosをthen、catch、finallyで成功時のコンソール


# HTTPリクエストが失敗する場合

APIのURLを存在しないものに変更することで、HTTPリクエストが失敗する場合をみていく。

index.js

  .get('http://localhost:3001/xxx')

APIは存在しないため404エラーになる。
HTTPリクエストが成功する場合と同様に、GET前は「ローディング表示開始」と表示される。しかし、thenは通らなくなるため「成功です」と出力されず、catchに書かれている「失敗です」が出力される。そして、HTTPリクエストの成功、失敗に関わらずfinallyが実行される。finallyは必ず実行されるため、API実行前に表示しておいたローディングを非表示にする処理などで使われる。

axiosをthen、catch、finallyで失敗時のコンソール

# axiosをasync、awaitとtry、catch、finallyで制御

さきほどまでのコードをasyncawaitを使って書き換える。
それに伴いエラーのハンドリングもtrycatchfinallyを使うようにする。 axios.getの前にawaitを書いておき、trycatchで囲む。finallycatchに連なる形で書く。そして、関数の前にasyncを記載する。

# HTTPリクエストが成功する場合

index.js

import '@babel/polyfill';
import axios from 'axios';

const getPosts = async () => {
  console.info('ローディング表示開始')
  try {
    const response = await axios.get('http://localhost:3001/posts');
    console.info('成功です', response.data);
  } catch(error) {
    console.error('失敗です', error);
  } finally {
    console.info('ローディング表示終了');
  }
};

(async () => {
  console.info('API呼び出し前');
  await getPosts();
  console.info('API呼び出し後');
})();

ブラウザのコンソールで確認すると、非同期でAPIの呼び出しを行うgetPosts関数の処理が終わってから「API呼び出し後」が出力されていることがわかる。
そして、tryfinallyのコードが実行されている。

async、awaitで成功

# HTTPリクエストが失敗する場合

asyncawaitでもAPIのURLを存在しないものに変更することで、HTTPリクエストが失敗する場合をみていく。

    const response = await axios.get('http://localhost:3001/xxx');

HTTPリクエストが成功した場合と同様、非同期処理が終わった後に後続の「API呼び出し後」が出力されている。try内のHTTPリクエストが失敗したaxios.get以降の「成功です」は出力されず、 catchfinallyが実行される。
これでthencatchで扱っていたときと同様にasyncawaitでもHTTP通信を制御できることがわかった。

async、awaitで失敗