這篇文章想跟大家一起重新溫習一下關於使用 try...catch
進行異常捕獲的一些知識點。為了提升大家的閱讀興趣,我們先來做三個小練習題,如果你都做對的話,那麼表明你這一部分的知識掌握的很熟練,可以不用讀這篇文章啦~ 如果做錯了某道題的話,說明我們還有一些知識點需要再次鞏固一下,話不多說,我們先來看看這三道題:
function doTask () {
try {
console.log('1 in try block');
throw '2 test error';
return 3;
} catch (e) {
console.log('4 in catch block');
console.log(e);
return 5;
} finally {
console.log('6 in finally block');
return 7;
}
}
// 控制檯輸出的結果是什麼呢?
console.log(doTask());
function doWork () {
try {
console.log(1);
try {
console.log(2);
throw 3;
} finally {
console.log(4);
}
console.log(5);
} catch (e) {
console.log(e);
}
}
// 控制檯輸出的結果是什麼呢?
doWork();
function doRepeat () {
for (let i = 0; i < 2; i++) {
try {
if (i === 1) {
continue;
}
console.log(`${i} in try`);
} finally {
console.log(`${i} in finally`);
}
}
}
// 控制檯的輸出結果是什麼呢?
doRepeat();
上面的每一道題目都有它的小心思,一不小心就容易出錯。大家可以把自己測試的結果在評論區曬一曬。
如果你做錯了某道題,沒有關係,透過這篇文章我們一起再來複習一下相關的知識點。看到就是學到,讓我們開始吧。
try/catch/finally 的各種形式
try...catch
這種形式應該是我們在開發中最經常使用的形式了,當我們需要執行一些可能會丟擲錯誤的方法或者語句的時候,就會使用 try
將要執行的語句包裹起來,然後緊跟著 catch
去捕獲對應的異常。形式大概如下所示:
try {
// 有機率丟擲異常的操作
someErrorProneFn();
} catch (e) {
// 列印並且上報接日誌
logAndReportError(e);
}
我們也可以判斷 e
的型別,進而根據不同的錯誤型別進行不同的操作。
還有一點需要注意的是,catch
也是可以不接收異常資訊的。如下所示:
try {
// 有機率丟擲異常的操作
someErrorProneFn();
} catch {
// 不管上面的操作出現了什麼問題,都直接通知使用者
remindUser();
}
這種形式大家在平時開發中用的比較多,所以用起來是沒有什麼大問題的。
try...finally
try {
connectChannel();
doSomething();
} finally {
// 最後做一些清理的工作
cleanup();
}
第二種形式就是 try...finally
,這種形式估計大家平時用的比較少;這種形式一般用於執行一個操作,一般情況下這個操作可能是佔用記憶體比較高,或者是操作完需要進行記憶體回收或者關閉連線或者是關閉資料庫之類的。但是我們可能不是很關注操作過程中出現的異常。 那麼我們就可以在操作完成的時候,進行一些尾部的處理工作。
當前 try
程式碼塊裡面的異常還是會丟擲到外部的,需要外部進行捕獲和處理。這一點知識點跟上面的第二、第三題有一些關聯,我們稍後會詳細跟大家講解一下。
try...catch...finally
這種形式是異常捕獲最全的一種形式,我們透過 try
程式碼塊執行容易出錯的程式碼,透過 catch
程式碼塊捕獲異常,根據異常的型別分別進行不同的處理;透過 finally
程式碼塊執行最後的操作,一般用來做一些清理的工作。
try...catch...finally
我們以這種最全的形式作為示例,來講解幾個核心的知識點。
try
程式碼塊的執行是最先執行,只有當try
程式碼塊丟擲了異常,才會進入catch
程式碼塊。try...finally
中,如果try
程式碼塊中丟擲了異常,那麼這個異常會被外層最近的catch
程式碼塊捕獲。finally
程式碼塊總是會執行的,不管try
程式碼塊是否丟擲異常,以及catch
程式碼塊是否執行。finally
程式碼塊的執行時機:如果try
/catch
程式碼塊中包含流程控制語句(例如:return
,break
,continue
,throw
),那麼finally
程式碼塊會在這些流程控制語句執行之前執行。如果不包含流程控制語句,則在對應的程式碼塊執行完成之後開始執行。如果finally
程式碼塊包含流程控制語句,那麼finally
程式碼塊的流程控制語句就會先執行,進而替換掉原來try
/catch
中的流程控制語句。這個需要注意。
如果上面的文字描述對你來說還是有點不好理解,那麼我們可以看下面這個我製作的流程圖,希望可以加強你對這一塊知識的理解。
上面這些知識點很重要, 如果你知道上面這些知識點的話,那麼上面那三道題對你來說是很容易的事情啦。我們來詳細講解一下上面的三道題目。
第1道題目
- 首先執行
doTask
函式,進入try
block,列印1 in try block
;然後執行throw '2 test error'
,後面的return 3
不會再執行,所以try
block 就只列印了1 in try block
。 - 因為
try
block 丟擲了異常,所以會進入catch
block,然後列印4 in catch block
,接著列印e
,也就是2 test error
,接著準備開始執行return 5
,因為return 5
是流程控制語句,所以接下來開始執行後面的finally
block。 - 接著列印
6 in finally block
,又因為這個finally
block 含有流程控制語句,所以會替換掉catch
裡面的return 5
。 - 最後列印
doTask
函式的返回值:7
所以最終的運算輸出的結果是:
1 in try block
4 in catch block
2 test error
6 in finally block
7
第2道題目
- 執行
doWork
函式,進入try
block,開始列印:1
; - 進入內部的
try
block,列印:2
;開始執行throw 3
之前,因為throw 3
是流程控制語句,所以首先執行finally
block 裡面的語句,列印:4
; - 然後開始執行
throw 3
,後面的console.log(5)
執行不到,直接跳過。異常被最近的外層catch
捕獲,列印:3
。
所以最終的運算輸出結果是:
1
2
4
3
第3道題目
- 執行函式
doRepeat
,進入第一次迴圈,i
的值為 0;第一次迴圈try
/finally
block 都執行,所以先列印:0 in try
,然後列印:0 in finally
; - 第二次迴圈,
try
block 中遇到了continue
,因為continue
是流程控制語句,所以接下來先執行finally
block 的語句,列印:1 in finally
;然後執行continue
,迴圈結束。
所以最終的運算輸出結果是:
0 in try
0 in finally
1 in finally
前端的好厚米,關於 try/catch/finally
的內容到這裡就結束啦,如果你有什麼建議和意見歡迎在文章下面評論和留言;如果這篇文章你覺得對你有幫助,那就幫忙點贊轉發一下,我們下次再見。
參考連結: