Vue全域性錯誤提示的一點思考
我在寫程式碼的時候,很討厭寫異常路徑的各種解決辦法,最好的一直都是走happy path。
在寫Vue的時候,網路請求部分往往需要處理成功部分,失敗部分,不管成功還是失敗都要執行的部分。js有關鍵字async
和await
,可以解決部分非同步回撥的問題,當然也就無法像之前xx.then().catch().finally()
這樣寫了。如果報了異常,該如何解決了?在RESTful大行其道的時代,每次都得判斷錯誤碼,然後分別處理,這是很煩的一個事情。由於某些特殊原因,會保證http
響應碼是200,而後端往往封裝了一個類似的響應。借鑑RESTful的一些思想,比如這樣一個類
import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; /** * 所有前後端分類的請求,返回這個包裝類 */ @AllArgsConstructor @NoArgsConstructor @Getter @Setter public class Response<T> extends BaseObject{ /** * 自定義響應碼 */ private Integer code; /** * 響應資料 */ private T data; /** * 正常情況下,對沒有響應資料的情況一個解釋 * 出錯情況下,前端顯示文案 */ private String msg; private static final Integer OK = 200; private static final Integer BAD_REQUEST = 400; public static Response success(){ return new Response(OK, null, null); } public static Response success(Object data){ return new Response(OK, data, null); } public static Response success(Object data, String msg){ return new Response(OK, data, msg); } public static Response fail(String msg) { return new Response(BAD_REQUEST, null, msg); } }
在前端寫程式碼的時候,非常容易寫出
if (res.data.code === 200) {
const data = res.data.data
// xxx
}
// 省略其他情況判斷
如果在業務程式碼中充滿這樣的程式碼,無疑是非常痛苦的。寫的時候還行,copycopy
,Review的時候就發現重複的東西非常多,在一般的錯誤,如果後端把文案寫好,很可能就是一個簡單的提示,如
this.$Message.error(res.data.msg)
前後端的互動一般通過http請求,一般情況下,都不會把請求url直接寫在Vue元件內,都會封裝成一個方法,以iview-admin為例,類似如下
- user.js檔案
export const login = (data) => {
return axios.request({
url: 'auth/login',
data,
method: 'post'
})
}
先不討論登入怎麼RESTful去寫,全當咱用的也是偽RESTful,然後魔改的方式使用。
然後在登入介面中使用
login.vue
import userAPI from '@/api/user' // 省略其他 async handleLogin(){ const data = // 從某些地方拼湊出登入需要的data const res = await userAPI.login(data) // 如果是成功的話 if (res.data.code === 200) { // 登入成功 // 拿token什麼的 const token = res.data.data.token } if (res.data.code === 400){ // 登入失敗 // 提示錯誤什麼的 } }
其實寫了這麼多,我覺得程式碼就寫了兩行
const res = await userAPI.login(data)
const token = res.data.data.token
什麼錯誤處理,太難受了
如果能讓我直接處理資料就好了,比如
const token = await userAPI.login(data)
就這樣一句解決,如果出了錯誤,應該是在專門的,統一的一個地方去弄。
於是乎,好像是一個很不錯的東西
在iview-admin中封裝了一個請求類,可以在攔截器中做點手腳
- axios.js
import axios from 'axios'
import store from '@/store'
// import { Spin } from 'iview'
const addErrorLog = errorInfo => {
const { statusText, status, request: { responseURL } } = errorInfo
let info = {
type: 'ajax',
code: status,
mes: statusText,
url: responseURL
}
if (!responseURL.includes('save_error_logger')) store.dispatch('addErrorLog', info)
}
class HttpRequest {
constructor (baseUrl = baseURL) {
this.baseUrl = baseUrl
this.queue = {}
}
getInsideConfig () {
const config = {
baseURL: this.baseUrl,
headers: {
//
}
}
return config
}
destroy (url) {
delete this.queue[url]
if (!Object.keys(this.queue).length) {
// Spin.hide()
}
}
interceptors (instance, url) {
// 請求攔截
instance.interceptors.request.use(config => {
// 新增全域性的loading...
if (!Object.keys(this.queue).length) {
// Spin.show() // 不建議開啟,因為介面不友好
}
this.queue[url] = true
return config
}, error => {
return Promise.reject(error)
})
// 響應攔截
instance.interceptors.response.use(res => {
this.destroy(url)
const { data, status } = res
return { data, status }
}, error => {
this.destroy(url)
let errorInfo = error.response
if (!errorInfo) {
const { request: { statusText, status }, config } = JSON.parse(JSON.stringify(error))
errorInfo = {
statusText,
status,
request: { responseURL: config.url }
}
}
addErrorLog(errorInfo)
return Promise.reject(error)
})
}
request (options) {
const instance = axios.create()
options = Object.assign(this.getInsideConfig(), options)
this.interceptors(instance, options.url)
return instance(options)
}
}
export default HttpRequest
在響應的攔截器中加的東西,去判code
// 響應攔截
instance.interceptors.response.use(res => {
this.destroy(url)
const {data, status, headers} = res
if (status === 200) {
if (data.code === 200) {
return data.data
} else {
}
} else {
}
}, error => {
this.destroy(url)
})
這樣倒是能夠直接拿到我需要的資料,本身真實的資料就是封裝了,現在只不過是拆開封裝。
要是出了錯怎麼辦?
在百度一番後,發現了兩篇文章
Vue實戰(五)網路層攔截器與全域性異常資訊展示
Vuex如何做通用的全域性錯誤資訊?
發現利用vuex的一些特性加上watch是一個比較好的做法
寫一個存放錯誤的store
error.js
export default {
state: {
flag: false,
msg: ''
},
mutations: {
changeFlag(state, msg) {
state.msg = msg
state.flag = !state.flag
},
},
getters: {
errorFlag: state => state.flag,
errorMsg: state => state.msg
},
actions: {
changeFlag({commit, rootState}, msg) {
commit('changeFlag', msg)
}
}
}
這裡有一個flag
,僅僅是用來讓別人watch
的
然後在app.vue
中監聽flag
變化,只要flag
一變化,立馬顯示錯誤資訊
computed: {
...mapGetters([
'errorFlag',
'errorMsg'
]
)
},
watch: {
errorFlag (nv, ov) {
this.$Message.error(this.errorMsg)
}
},
最後axios
攔截器配合一下
const {data, status, headers} = res
if (status === 200) {
if (data.code === 200) {
return data.data
} else {
store.dispatch('changeFlag', data.msg)
}
} else {
store.dispatch('changeFlag', `網路錯誤: ${status}`)
}
這樣就可以全域性提示,而不會影響到業務程式碼的happy path。
最後這樣做的問題
任何一個新的改變都會在帶來好處的同時,帶來一些麻煩,這樣做了,引入什麼新的問題呢?下次再寫吧!
未完待續…