#概要
アウトプットとしてRails × Vue.jsでアプリ開発を行うなかで実装に詰まり、改善の過程で非同期通信の仕組みやaxiosの処理の流れについて知見が深まったため備忘録として残します。
##非同期通信って何?
従来、ユーザーがWebブラウザの閲覧を行うには
①Webブラウザ(クライアント)がウェブサイト(サーバー)にHTTPリクエストを送る
②サーバーが処理を行い、その結果をブラウザに返す
③ブラウザがそれを表示することで、ユーザーがウェブページを見ることができる
という処理の流れがあり、それを行う最中には他の処理(スクロールして他の情報を閲覧する、等)が出来ない、というのが一般的でした
ところが、非同期なHTTP通信という手段が開発されたことで、
HTTPリクエストを送る
↓
レスポンスが帰ってくる
という処理の間もブラウザを操作出来、そしてサーバーからレスポンスを受け取ると、続けて処理を実行してくれるため、UXが大幅に向上しました。
axiosは、そんな非同期通信を行いたいときに容易に実装できるJavaScriptのライブラリです。
axiosを使うことで様々なAPIに接続し、データの取得/更新/削除等を出来るのですが、理解が浅いまま実装を進めたことで不具合が起き、2〜3日溶かしてしまいました...
詰まった原因を振り返ってまとめていきたいと思います。
尚、今回の内容は下記環境で実行しております。
◯実行環境
・Ruby: 2.5.1
・Rails: 6.0.3
・Vue.js: 2.6.12
##実際に試してみた
テーブルにある下記ユーザー情報を、HTML上のボタンをクリックして全て取得し一覧表示する、という例をとって話を進めていきます。
name | age |
---|---|
太郎 | 7歳 |
花子 | 10歳 |
武 | 12歳 |
こちらに今回使用するファイルを列挙します。
# api/users_contoller
class Api::UsersController < ApplicationController
def index
users = User.all
render json: users
end
end
# homes_controller.rb
class HomesController < ApplicationController
def index
end
end
Rails.application.routes.draw do
root "homes#index"
namespace :api, {format: 'json'} do
resources :users, only: [:index]
end
end
<div id="app"></div>
<%= javascript_pack_tag "app_vue" %>
<template>
<div id="app">
<button @click="getUserData">ユーザー情報を取得</button>
<hr>
<p>ユーザー一覧</p>
<ul>
<li v-for="user in users" :key="user.id">
{{ user.name }}さん: {{ user.age }}歳
</li>
</ul>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
users: null,
errorNumber: null
}
},
methods: {
getUserData() {
axios
.get('/api/v1/admin/user')
.then(response => {
console.log(response)
this.users = response.data
})
.catch( (error) => {
alert(`${error.response}番のエラーが発生しました`)
});
}
},
};
</script>
//エントリーポイント
import Vue from "vue";
import App from "../app.vue";
import axios from "axios";
axios.defaults.headers.common = {
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
};
import VueAxios from "vue-axios";
Vue.use(VueAxios, axios);
document.addEventListener("DOMContentLoaded", () => {
let vm = new Vue({
el: '#app',
render: (h) => h(App),
})
});
期待通りユーザー情報が一覧表示されているのが確認出来ます。
ボタンをクリックした際の処理
methods: {
getUserData() {
axios
.get('/api/users') //urlを指定し、バックエンドのapi/users_controllerへとリクエストを投げる
.then(response => ( //*1 非同期処理が正常に処理されたらthenの中身が処理される(返ってきたデータをresponseとして受け取り、componentのdataプロパティに渡す
this.users = response.data
))
.catch( (error) => { //*2 非同期処理が失敗したらcatchの中身が処理される(エラーの内容をalertに出力する)
alert(`${error.response.status}番のエラーが発生しました`)
});
}
},
めちゃくちゃざっくりですが、このような処理が行われています。
理解を深めるため、上記コメントの*1 *2の部分について、もう少し掘り下げて説明していきます。
*1 thenの処理について
thenとは、非同期通信が無事終わったことを受けとってから実行される処理のことです。
また、thenやcatch(次項で説明)の処理を理解するうえで重要なのが、Promissというオブジェクトです。
今回のコードには書かれていないのですが、非同期処理が実行されるとき、裏方ではPromissという特殊なオブジェクトが作成され、Promissが下す命令によって処理が進んでいきます。いわば現場監督のようなものです。
急に出てきたけどPromissって何?という方は、以下の記事が理解しやすかったので、ぜひ参考にしてみてください!
以下、記事の引用
Promiseには、PromiseStatusというstatusがあり、3つのstatusがあります。
pending: 未解決 (処理が終わるのを待っている状態)
resolved: 解決済み (処理が終わり、無事成功した状態)
rejected: 拒否 (処理が失敗に終わってしまった状態)
引用文にあるとおり、作成されたPromissのstatusが処理状況によって変更され、その処理がrevolveになったときに処理されるのがthenなのです。
.then(response => (...))
thenのなかでは、返ってきたデータを『response』というオブジェクトとして受け取り、その後の処理に使用しています。
今回の記事では便宜上この名前にしましたが、他の名前でも問題なく処理されます。
またここで重要なのが、__thenとして処理させたいコードは必ず () もしくは {} のなかに書く__ということです。
❌ .then(response => (const users = response.data)) //()のなかに入っていないので正しくデータを取得できない
this.$store.dispatch('...', users)
❌ .then(response => (const users = response.data) //2つ以上処理を書く場合は{}にまとめる
this.$store.dispatch('...', users)
)
⭕️ .then(response => {
const users = response.data
this.$store.dispatch('...', users)
}
私はここをしっかり理解せずthenの外で処理を書いていたため、
非同期通信の完了を待たずに実行→データが取れず不具合に悩まされてしまいました...
*2のcatchの処理について
試しにaxios.getのurlを api/user
とタイポした状態で実行してみます。
notfound(404)エラーが発生し、エラー用に書いたalertも表示されています。
これは、タイポにより処理が失敗 → Promissのstatusがrejectedになったことでcatchが処理されたためです。
catchのなかの『error』もthenの『response』と同様、名前を変更しても処理されるようになっています。
.catch((error) => {
alert(`${error.response.status}番のエラーが発生しました`)
});
ここで気になるのがerror.response.status
の部分です。この『status』とは何を指しているのでしょう?
実は、Promissとして返ってくるレスポンスデータは、詳細内容を以下のプロパティに分けて格納する仕組みになっています。
response.data // レスポンスデータ
response.status // ステータスコード(今回のエラーの場合404 4xxのステータスコードはクライアント側でエラーが発生していることを表しています)
response.statusText // ステータステキスト
response.headers // レスポンスヘッダ
response.config // コンフィグ
様々なプロパティがあり、どれも必要な情報が入っています。
まずはresponse.dataにデータが返ることを覚えておきましょう。
(ごめんなさい、私もちゃんと理解出来ていません...)
そして更に、エラーとなる3つの原因を以下のように条件分岐させることが出来ます。
これにより、処理の原因を絞り込みやすくすることが出来ます。
// ステータスコードが成功時のステータス(2xx番)以外だと、エラーとして処理されます
.catch(function (error) {
if (error.response) { // 2XXの範囲外
} else if (error.request) { // 要求がなされたが、応答が受信されなかった
} else { // トリガーしたリクエストの設定に何かしらのエラーがある
}
});
##終わりに
以上、非同期処理やaxiosについて学んだことをまとめてみました!
もし認識が間違っている内容がありましたら、お手数ですがコメント欄にてご指摘いただけると幸いです。