try/catch/finally:“前端的好厚米,我覺得你們不夠了解我呀~”

dreamapplehappy發表於2023-02-02

這篇文章想跟大家一起重新溫習一下關於使用 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 程式碼塊中包含流程控制語句(例如:returnbreakcontinuethrow),那麼 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 的內容到這裡就結束啦,如果你有什麼建議和意見歡迎在文章下面評論和留言;如果這篇文章你覺得對你有幫助,那就幫忙點贊轉發一下,我們下次再見。

參考連結:

相關文章