axios Error Handle

這篇文章沒甚麼特別的,只是純粹應用了一次axios的錯誤處理而已,希望表達在應用程式內,將重複程式碼抽象出來利用的範例

基本語法範例

axiosjquery語法都還蠻像的,經由config設定發出請求的網址、資料,像是下面這樣

1
2
3
4
5
6
7
8
axios({
method: 'post',
url: '/user/12345',
data: {
firstName: 'bar',
lastName: 'foo',
},
});

也可以經由別名來處理網址、資料,若是需要也可以再添加config

1
2
3
4
5
6
7
8
9
10
axios.post(
'/user/12345',
{
firstName: 'bar',
lastName: 'foo',
},
{
/*config here*/
}
);

官網已經寫得很清楚了,細節就請移步官網瀏覽

情境

當我們的程式碼越來越多、也越來越複雜的時候,為了維護方便,我們可能會自然而然地將架構逐漸演化,假設是一個登陸頁面好了,除了基本的頁面進入點index.js,可能還會在裡面有一個Service來處理這個頁面的一些操作,因為Service只是負責處理邏輯,一些比較細節的處理則是再放給HelperUtiliyAdapter之類的去做,而為了便於維護,我也會使用AxiosHelper去將一些行為包裝起來,所以輸入帳號密碼登入的操作會是像這樣

1
2
3
4
5
<form>
Name:<input type="text" name="name" id="name" v-model="form.name" />
Pass:<input type="text" name="pass" id="pass" v-model="form.pass" />
<button v-on:click="loginHandler">Login</button>
</form>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// index.js
// import ....

new Vue({
data() {
return {
form: {
name: '',
pass: '',
},
};
},
methods: {
async loginHandler() {
sweetAlertHelper.ShowLoading();
let { isSuccess, message } = await memberService.login({ ...this.form });
sweetAlertHelper.CloseModal();
if (isSuccess) {
// 登入成功,跳轉頁面
window.location.href = '/Member';
} else {
// 登入錯誤,將資料傳遞給Log服務,將錯誤記錄下來
logService.loginFailed({ ...this.form }, message);
// 顯示錯誤訊息,告知使用者登入錯誤,訊息由後端控制
sweetAlertHelper.showError(message);
}
},
},
});
1
2
3
4
5
6
7
8
// MemberService.js
import MemberAdapter from './MemberAdapter.js';
const MemberService = {
login({ name, pass }) {
return MemberAdapter.login({ name, pass });
},
};
export default MemberService;
1
2
3
4
5
6
7
8
// MemberAdapter.js
import AxiosHelper from './AxiosHelper.js'
import MemberAdapter = {
login({name,pass}){
return AxiosHelper.sendRequest('/Login',{name,pass})
}
}
export default MemberAdapter;
1
2
3
4
5
6
7
8
// axiosHelper.js
import axios from 'axios';
const AxiosHelper = {
sendRequest: async (url, sendData) => {
return (await axios.post(url, sendData)).data;
},
};
export default AxiosHelper;

按照上面的程式碼來看,登入的回應如果是正確或失敗,都是有動作的,乍看之下沒甚麼問題,但是如果後端伺服器沒有回應呢?最常見的就是後端很忙,結果前端請求就一直掛在那邊變成 timeout,然後因為程式碼在呼叫後端之前,透過SweetAlertHelper將畫面墊了一層遮罩,秀出 Loading 畫面,避免讓使用者重複送出請求,結果因為後端 Timeout,則前端 Js 就掛掉了,於是使用者看到的畫面就是一直轉圈圈的 Loading 畫面

上面程式碼沒有實際跑過,只是為了讓大家比較理解情境隨手寫的,跑不起來的

解決方案

既然我們針對的情境是要解決 timeout,那我們就直接使用官方所提供的錯誤處理不就好了?

是的,沒有錯,我們就直接套用就行了,OK,結案。

因為我們已經將所有使用到 axios 的東西都封裝在 AxiosHelper 裡面了,所以我們也只需要改一個地方就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// axiosHelper.js
import axios from 'axios';
const AxiosHelper = {
sendRequest: async (url, sendData) => {
try {
// 多了一個 {timeout: 5000} 的 option config
return (await axios.post(url, sendData, { timeout: 5000 })).data;
} catch (error) {
// 顯示錯誤訊息,告知使用者登入錯誤,訊息由後端控制
sweetAlertHelper.showError('伺服器忙碌,請稍後再試');
// throw error 中斷後面的程式不再執行
throw error;
}
},
};

這樣子的話,在原先程式碼的部分,就不會繼續往下執行,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
async loginHandler(){
sweetAlertHelper.ShowLoading()
let {isSuccess, message} = await memberService.login({...this.form})
/** 這以下就不會執行了

sweetAlertHelper.CloseModal()
if (isSuccess) {
// 登入成功,跳轉頁面
window.location.href="/Member"
} else {
// 登入錯誤,將資料傳遞給Log服務,將錯誤記錄下來
logService.loginFailed({...this.form}, message)
// 顯示錯誤訊息,告知使用者登入錯誤,訊息由後端控制
sweetAlertHelper.showError(message)
}

*/
}

原本的程式碼就會再呼叫完畢後,拋出例外,直接中斷掉,使用者也能看到畫面顯示伺服器忙碌,同時我們也只需要改一個地方,其他頁面都不需再調整,好吧,技術本身沒甚麼難度,但是共用的抽象出來才是我要表達的

延伸議題

  1. #研發解決方案#易車前端監控系統:這篇文章是某一天我逛到的,它們自己做了一套監控系統,在錯誤處理的部分,也可以將這些資訊像它們一樣,不管是透過自己建立,或者是用現有 Open Source 解決方案,都是不錯的,不過我沒有再研究下去,有興趣的朋友可以點進去看看

  2. 使用 Axios 你的 API 都怎麼管理:這篇則是一位知名的前端 Youtuber 寫的一篇關於 axios 的東西,概念上是不錯的,他這篇文章主要針對的情境是你可能服務來自好幾個Domain所以會需要這樣做,但我碰到的情境這部分會在後端就整合起來而不是在前端來做。所以他的辦法就比較不合我用,但是概念上都是為了便於管理,也是一個不錯的案例解決方案,提供大家參考

這也是為什麼我會說要看情況套用解決方案,別人的best practice不一定適合你,多看多參考,你會有自己的想法的