課題
Vuejs利用時に、HTTPレスポンスの例外処理を共通化したい。 各画面の各リクエストごとに、例外処理を実装するのは避けたい。
解決方法
HTTPクライアントライブラリのAxiosの、interceptorsの仕組みを利用する。
検証環境
package.json
"dependencies": {
"axios": "^0.17.1",
"vue": "^2.5.2",
"vue-router": "^3.0.1",
"vue-toasted": "^1.1.24"
},
モックサーバを用意する
検証用に、エラーを返すモックサーバを用意する。
$ npm install -D json-server
モック定義のファイル(mock.js)を用意する。
const jsonServer = require('json-server')
const server = jsonServer.create()
const middlewares = jsonServer.defaults()
server.use(middlewares)
server.listen(3000, () => {
console.log('JSON Server is running')
})
server.get('/unauthorized', (req, res) => {
res.status(401).jsonp({
message: "unauthorized"
})
})
server.get('/systemerror', (req, res) => {
res.status(500).jsonp({
message: "something wrong"
})
})
モックサーバを起動する。
$ node mock.js
JSON Server is running
システムエラーの場合は、500を返す。
$ curl -i localhost:3000/systemerror
HTTP/1.1 500 Internal Server Error
X-Powered-By: Express
Vary: Origin, Accept-Encoding
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
X-Content-Type-Options: nosniff
Content-Type: application/json; charset=utf-8
Content-Length: 32
ETag: W/"20-fhnUB5BwaOsQsXyG8exFr0MEGzY"
Date: Wed, 17 Jan 2018 13:16:01 GMT
Connection: keep-alive
{
"message": "something wrong"
}
認証エラーの場合は、401を返す。
$ curl -i localhost:3000/unauthorized
HTTP/1.1 401 Unauthorized
X-Powered-By: Express
Vary: Origin, Accept-Encoding
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
X-Content-Type-Options: nosniff
Content-Type: application/json; charset=utf-8
Content-Length: 29
ETag: W/"1d-1AQxoXvOBStoEV/A43KaFU1XEOg"
Date: Wed, 17 Jan 2018 13:16:42 GMT
Connection: keep-alive
{
"message": "unauthorized"
}
エラーメッセージの表示方法
今回はエラーメッセージの表示に、vue-toastedというライブラリを利用する。 vue-toastedは、以下のようなトースト表示ができるライブラリ。
Axiosでinterceptorsを実装する
interceptorsによって、HTTPレスポンスの共通処理を作成していく。
import Vue from 'vue'
import Axios from 'axios'
const http = Axios.create({
// for cors
withCredentials: true
})
http.interceptors.response.use(function (response) {
}, function (error) {
// 認証エラー時の処理
if (error.response.status === 401) {
Vue.toasted.clear()
Vue.toasted.error(error.response.data.message)
// システムエラー時の処理
} else if (error.response.status === 500) {
Vue.toasted.clear()
Vue.toasted.error(error.response.data.message)
}
return Promise.reject(error)
})
export default http
Axiosをimportし、Vueのprototypeに設定することで、共通処理が設定済みのAxiosを$httpで利用できるようになる。(グローバルな空間にオブジェクトをつっこむのが嫌であれば、各画面でimportしても良い)
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import Axios from './axios'
import Toasted from 'vue-toasted'
Vue.prototype.$http = Axios
Vue.use(Toasted)
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
APIアクセス時には、$httpを利用する。
<template>
<div id="app">
<button type="button" @click="callUnauthorized">unauthorized</button>
<button type="button" @click="callSystemError">system error</button>
</div>
</template>
<script>
export default {
name: 'App',
methods: {
callUnauthorized: function () {
this.$http.get('http://localhost:3000/unauthorized')
},
callSystemError: function () {
this.$http.get('http://localhost:3000/systemerror')
}
}
}
</script>
実行結果
HTTPレスポンスの共通処理が実行されて、トーストが表示される。
注意点
今回の例だと1コンポーネントしかないので、トーストを表示しっぱなしにしている。 もしコンポーネントが切り替わったときにトーストを消したい場合は、vue-router のナビゲーションガードやコンポーネントのdestroyedフック内で Vue.toasted.clear()
を呼び出すといい。
参考: ナビゲーションガード、 ライフサイクルフック
サンプルアプリケーション
githubに置きました。
参考: Axios Sample