從 JavaScript 角度學 Python(25) - 例外處理

前言

接下來我們要聊一個非常非常重要的東西,也就是關於錯誤的處理,而這個處理又稱之為例外處理。

例外處理概念

首先先聊聊例外處理是什麼,通常我們在開發系統的時候不免會遇到一些錯誤狀況,例如:使用者網路不穩定、使用者輸入錯誤的資料、忘記宣告變數、輸入到一半颱風來吹斷電線桿停電或者是上網到一半被隔壁屁孩射斷網路線這種諸如此類的狀況(之前真的有朋友跟我說他的網路線被屁孩射斷),因此如果沒有做好一些錯誤機制的防範的話,那麼系統在上線時是很容易降低客戶對於該系統的信任度,所以錯誤的處理上是非常重要的。

那麼為了避免這種狀況我們通常就會使用例外處理的方式處理,如果已經有開始撰寫 ES7 語法的人,相信非常的熟悉 asyncawait 語法,想當然你也一定會使用 try...catch 語法,下面簡單寫一小段:

1
2
3
4
5
6
7
8

async function getData () {
try{
const res = await axios.get('xxxx');
} catch (error) {
console.error(error);
}
}

上面的程式碼簡單來講,就是當 AJAX 因意外失敗或者網路中斷時,就會自動改跑 catch 區塊,但是這邊要注意如果你是用於前端畫面給使用者看的話,請不要使用 console.log 當作錯誤提示,因為普通的使用者根本不知道什麼是網頁開發者工具(devToole),因此這邊只是一個範例程式碼而已。

不知道為什麼系統出錯的使用者

而上方是一個簡單的例外錯誤處理,接下來我們將會進入 Python 環節了解 Python 的例外處理是如何撰寫的唷!

例外處理

一開始先我們刻意製造一個錯誤狀況:

1
2
3
4
5
6
def sayHello():
print('Hello')

print(myName)

sayHello()

上面的程式碼看起來很正常對吧?但實際上執行是會出現「NameError: name 'myName' is not defined」的錯誤唷~

嚇到吃手手

oh!如果你問我為什麼會出現這個錯的話,罰你回去「從 JavaScript 角度學 Python(4) - 型別與變數」章節重看,因為這章節我有解釋唷~

那麼如果你這邊有使用例外處理的話,就可以避免程式碼因為錯誤而導致中斷,而讓後面其他的程式碼可以正常運作:

1
2
3
4
5
6
7
8
9
def sayHello():
print('Hello')

try:
print(myName)
except:
print('你忘記宣告變數了。')

sayHello()

體會到例外處理好處的我妻善逸

針對錯誤類型的例外處理

除此之外你還可以針對錯誤類型來給予例外處理,什麼意思呢?舉例來講剛剛的範例程式碼會出現「NameError: name 'myName' is not defined」的錯誤訊息,而這個錯誤類型是 NameError,所以你可以特別針對 NameError 客製化例外處理:

1
2
3
4
5
6
7
8
9
10
11
def sayHello():
print('Hello')

try:
print(myName)
except NameError:
print('你忘記宣告變數了。')
except:
print('單純只是一個錯誤。')

sayHello()

但是錯誤類型的例外處理非常非常多種,所以我建議可以閱讀這一篇了解還有哪些常見的錯誤類型。

那 JavaScript 有沒有類似這種針對錯誤類型的例外處理呢?

基本上有,只是寫法上比較不一樣,JavaScript 是必須使用 instanceof 語法來判斷錯誤類型,舉例來講,當你語法沒有宣告就存取的話是會出現 ReferenceError 錯誤,因此寫法如下:

1
2
3
4
5
6
7
8
9
try {
console.log(Ray); // 沒有宣告的變數,會出現 「Uncaught ReferenceError: Ray is not defined」
} catch(error) {
if(error instanceof ReferenceError) {
console.log('你存取一個不存在的變數唷');
}
}

console.log('程式碼依然正常運作');

那麼透過上述兩種程式碼來講,你比較喜歡哪一種呢~

我全都要

finally 處理

還有一種處理是 finally,通常這個最常見於跟遠端伺服器要資料之後要做特定的事情,舉例來講我們在撰寫 Vue 的時候,通常會使用 Loading 套件來解決在跟遠端伺服器 AJAX 過程的等待時間,進而盡可能的提升使用者體驗,這邊我就舉例一個很常見不好寫法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const app = {
data() {
return {
isLoading: false,
};
},
methods:{
async getData() {
try {
this.isLoading = true;
await axios.get('xxxxx');
this.isLoading = false;
} catch (error) {
console.log(error)
}
},
},
};

Vue.createApp(app).mount('#app');

上面寫法看似不好的原因在於,當 AJAX 若失敗的話,就會進入到例外處理 catch,但是關閉 isLoading 的方式卻是在 try 中,因此這個 Loading 狀態就永遠不會關閉,有些人會乾脆 trycatch 都撰寫 this.isLoading = false; 就像下方一樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const app = {
data() {
return {
isLoading: false,
};
},
methods:{
async getData() {
try {
this.isLoading = true;
await axios.get('xxxxx');
this.isLoading = false;
} catch (error) {
console.log(error)
this.isLoading = false;
}
},
},
};

Vue.createApp(app).mount('#app');

但是你有想過嗎?今天只是一個 this.isLoading = false; 可能還感覺不出什麼,但若程式碼邏輯較複雜、較多的話,難道你兩處都要寫嗎?

所以這邊就要來介紹 finally,只要你使用 finally 的話,那麼就只會需要寫一次即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const app = {
data() {
return {
isLoading: false,
};
},
methods:{
async getData() {
try {
this.isLoading = true;
await axios.get('xxxxx');
} catch (error) {
console.log(error)
} finally {
this.isLoading = false;
}
},
},
};

Vue.createApp(app).mount('#app');

因為不管成功與否 finally 必定就是會執行,這樣子也可以確保如果請求失敗時,也可以正常的關閉 Loading 狀態。

當然 Python 也是一樣的做法,不管怎麼樣如果你有一個必定要執行的動作,例如:關閉檔案或者是輸出某些訊息,那就可以使用 finally 來解決:

1
2
3
4
5
6
7
8
9
10
11
12
13
def sayHello():
print('Hello')

try:
print(myName)
except NameError:
print('你忘記宣告變數了。')
except:
print('單純只是一個錯誤。')
finally:
print('這是一定會執行的行為。')

sayHello()

所以你也可以把 finally 理解成「必定會執行」的區塊,這樣你學會了嗎?

你學會了嗎?

拋出

聰明的你或許馬上就會聯想到 JavaScript 的 throw,有一種狀況是比較特別,舉例來講我們在做 AJAX 新增、修改與刪除等操作時,後端可能不會吐相對應的 HTTP Code,有可能是吐給你 status 來判斷這一次新增資料的結果是 true or false,所以這時候我們就會使用 throw 將當前的狀態踢出到例外處理。

那為什麼會有這種狀況呢?假使後端不管成功與否都是吐給你 200 狀態碼,那 AJAX 判斷上當然會認為你是請求成功的,並不屬於失敗,所以就不會走 catch 的部分,以 JavaScript 角度來講,我們可能就會這樣寫:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const app = {
data() {
return {
isLoading: false,
};
},
methods:{
async getData() {
try {
this.isLoading = true;
const res = await axios.get('xxxxx');
if(!res.data.status) {
throw Error('錯誤');
}
} catch (error) {
console.log(error)
} finally {
this.isLoading = false;
}
},
},
};

Vue.createApp(app).mount('#app');

透過上面範例我們可以自己決定何時要拋出,但是在 Python 中就沒有 throw 的語法,取而代之的是使用 raise 語法,raise 非常好玩,如果你不加上要噴什麼樣的錯誤的話,預設會是拋出 RuntimeError,你可以試著單獨執行 raise 看看:

1
raise # RuntimeError: No active exception to reraise

因此透過這個預設的特性就可以使用前面所學的技巧設置一個例外錯誤訊息:

1
2
3
4
5
6
7
8
9
10
11
status = True

try:
if(status):
raise
except RuntimeError:
print('Error', RuntimeError)
except:
print('單純只是一個錯誤。')
finally:
print('這是一定會執行的行為。')

如果以上錯誤的處理你還不滿意的話,你也可以自己建立一個 class 來自定屬於自己的例外,而這一點建議可以直接參考官方文件。

那麼今天關於例外處理的介紹也就到這邊告一個段落哩。

作者的話

完蛋了,今天的「作者的話」我完全不知道該打什麼,想分享一下醉雞的製作過程,但是我發現前面我已經分享過了,後來思考後想再分享小瓦機器人被我捏爆的事情,結果我昨天也講過了,這一篇只好空下「作者的話」了。

參考文獻

關於兔兔們

兔法無邊

Liker 讚賞 (拍手)

如果這一篇筆記文章對你有幫助,希望可以求點支持或 牡蠣 鼓勵 (ノД`)・゜・。

Liker 是一個按讚(拍手)的讚賞機制,每一篇文章最多可以按五下(拍手),按讚過程你是完全不用付費的(除非你想要每個月贊助我 :D),你只需要登入帳號就可以開始按讚。
而 Liker 會依據按讚數量分配獎金給創作者,所以如果你願意按個讚我會非常感謝你唷。

Google AD

撰寫一篇文章其實真的很花時間,如果你願意「關閉 Adblock (廣告阻擋器)」來支持我的話,我會非常感謝你 ヽ(・∀・)ノ