JS 中 try/catch/finally 關鍵詞的使用解析

在 JavaScript 中,try/catch/finally是處理代碼異常的核心關鍵詞組合,用於捕獲程序運行時的錯誤、避免程序崩潰,並執行必要的收尾邏輯。try包裹可能拋出錯誤的代碼塊,catch捕獲並處理錯誤,finally無論是否發生錯誤都會執行(用於釋放資源、清理環境等)。這一組合是保障代碼健壯性的關鍵,尤其在異步操作、數據解析、接口請求等易出錯場景中不可或缺。

一、try/catch/finally 的基礎用法

1. 核心語法結構
try {
  // 可能拋出錯誤的代碼(try塊)
  riskyCode();
} catch (error) {
  // 捕獲錯誤並處理(catch塊)
  handleError(error);
} finally {
  // 無論是否出錯,都會執行的代碼(finally塊,可選)
  cleanUp();
}
  • try塊:必須存在,包裹易出錯邏輯,執行過程中若拋出錯誤,立即跳轉到catch塊;

  • catch塊:可選,接收錯誤對象(包含錯誤信息、類型、堆棧等),僅在try塊拋出錯誤時執行;

  • finally塊:可選,無論try塊是否出錯、catch塊是否執行,最終都會執行(即使try/catch中有return)。

2. 基礎示例:捕獲簡單錯誤
function parseJSON(str) {
  try {
    // 嘗試解析JSON(無效JSON會拋出SyntaxError)
    const data = JSON.parse(str);
    console.log("解析成功:", data);
    return data;
  } catch (error) {
    // 捕獲解析錯誤,輸出信息
    console.error("解析失敗:", error.message);
    return null; // 出錯時返回默認值
  } finally {
    // 無論解析成功/失敗,都會執行
    console.log("JSON解析操作完成");
  }
}

// 測試有效JSON
parseJSON('{"name":"張三","age":28}');
// 輸出:
// 解析成功: { name: '張三', age: 28 }
// JSON解析操作完成

// 測試無效JSON
parseJSON('{name:"張三"}'); // 缺少引號,無效JSON
// 輸出:
// 解析失敗: Unexpected token n in JSON at position 1
// JSON解析操作完成

二、try/catch 的核心特性

1. 捕獲特定類型的錯誤

catch塊可通過判斷error.name區分錯誤類型(如SyntaxError、TypeError、ReferenceError),實現精細化錯誤處理。

function riskyOperation() {
  try {
    // 模擬不同類型的錯誤
    // const num = 1 + undefined; // TypeError
    // console.log(undeclaredVar); // ReferenceError
    JSON.parse("{invalid json}"); // SyntaxError
  } catch (error) {
    switch (error.name) {
      case "SyntaxError":
        console.error("語法錯誤:", error.message);
        break;
      case "TypeError":
        console.error("類型錯誤:", error.message);
        break;
      case "ReferenceError":
        console.error("引用錯誤:", error.message);
        break;
      default:
        console.error("未知錯誤:", error.message);
    }
  }
}

riskyOperation();
// 輸出:語法錯誤: Unexpected token i in JSON at position 1
2. 手動拋出錯誤(throw)

try塊中可通過throw主動拋出錯誤(自定義錯誤信息 / 對象),由catch塊捕獲處理,實現業務邏輯的錯誤預判。

function validateUser(user) {
  try {
    if (!user.name) {
      // 主動拋出自定義錯誤(字符串類型)
      throw "用户名不能為空";
    }
    if (user.age < 0 || user.age > 120) {
      // 主動拋出錯誤對象(更規範)
      throw new Error("年齡必須在0-120之間");
    }
    console.log("用户驗證通過");
  } catch (error) {
    // 捕獲自定義錯誤
    console.error("用户驗證失敗:", error);
  }
}

validateUser({ name: "", age: 28 });
// 輸出:用户驗證失敗: 用户名不能為空

validateUser({ name: "張三", age: 150 });
// 輸出:用户驗證失敗: Error: 年齡必須在0-120之間
3. 嵌套 try/catch

try/catch支持嵌套,內層catch可處理內層try的錯誤,未捕獲的錯誤會向上拋給外層catch。

try {
  try {
    // 內層錯誤
    JSON.parse("{invalid}");
  } catch (innerError) {
    // 內層捕獲語法錯誤,處理後重新拋出
    console.error("內層捕獲:", innerError.message);
    throw new Error("內層解析失敗,向上拋出");
  }
} catch (outerError) {
  // 外層捕獲重新拋出的錯誤
  console.error("外層捕獲:", outerError.message);
}
// 輸出:
// 內層捕獲: Unexpected token i in JSON at position 1
// 外層捕獲: 內層解析失敗,向上拋出

三、finally 的核心特性

1. 必執行的收尾邏輯

finally塊的核心價值是 “無論如何都執行”,即使try/catch中有return、break、throw等流程控制,也不會跳過。

function testFinally() {
  try {
    console.log("try塊執行");
    return "try返回值"; // 有return,仍會執行finally
  } catch (error) {
    console.log("catch塊執行");
    return "catch返回值";
  } finally {
    console.log("finally塊執行"); // 必然執行
  }
}

console.log(testFinally());
// 輸出:
// try塊執行
// finally塊執行
// try返回值
2. 釋放資源的典型場景

finally常用於釋放資源(如關閉文件、清除定時器、取消網絡請求),避免資源泄漏。

function fetchDataWithTimeout(url) {
  let timer;
  try {
    timer = setTimeout(() => {
      throw new Error("請求超時");
    }, 5000);
    // 模擬接口請求(此處省略真實fetch邏輯)
    console.log("發起請求:", url);
    // 假設請求成功,清除定時器
    clearTimeout(timer);
  } catch (error) {
    console.error("請求失敗:", error.message);
  } finally {
    // 無論請求成功/超時,確保清除定時器
    clearTimeout(timer);
    console.log("定時器已清理");
  }
}

fetchDataWithTimeout("https://api.example.com/data");
// 輸出:
// 發起請求: https://api.example.com/data
// 定時器已清理

四、try/catch 的適用場景

1. 處理不可控的外部數據

解析第三方接口返回的 JSON、處理用户輸入的內容時,用try/catch捕獲格式錯誤,避免程序崩潰。

// 處理接口返回的JSON數據
async function handleApiResponse(response) {
  try {
    const data = await response.json();
    return data;
  } catch (error) {
    console.error("接口數據解析失敗:", error);
    // 返回默認數據,保證後續邏輯正常運行
    return { code: -1, msg: "數據解析失敗" };
  }
}
2. 兼容不同環境的 API

使用非標準 API(如某些瀏覽器專屬方法)時,用try/catch捕獲 “方法不存在” 的錯誤,實現降級處理。

function getLocalStorage(key) {
  try {
    // 某些環境(如微信小程序)可能無localStorage,或處於隱私模式
    return localStorage.getItem(key);
  } catch (error) {
    console.error("讀取localStorage失敗:", error);
    // 降級為cookie存儲
    return document.cookie.split(`${key}=`)[1] || null;
  }
}
3. 異步代碼的錯誤捕獲

try/catch可捕獲async/await中的異步錯誤(需包裹await語句),替代.catch()鏈式調用,讓異步錯誤處理更直觀。

async function fetchUser() {
  try {
    const res = await fetch("https://api.example.com/user");
    if (!res.ok) {
      // 主動拋出HTTP錯誤(狀態碼非2xx)
      throw new Error(`請求失敗:${res.status}`);
    }
    const user = await res.json();
    return user;
  } catch (error) {
    console.error("獲取用户信息失敗:", error.message);
    return null;
  }
}

五、使用注意事項

1. 避免過度捕獲錯誤

try/catch不應包裹所有代碼,僅用於 “可能出錯且無法提前預判” 的邏輯(如 JSON 解析、網絡請求);若錯誤可通過條件判斷避免(如if (obj) obj.key),則優先用條件判斷,而非try/catch(性能更優)。

// 不推薦:用try/catch捕獲可預判的錯誤
try {
  console.log(obj.key);
} catch (error) {
  console.log("obj不存在");
}

// 推薦:條件判斷提前規避
if (obj && obj.key) {
  console.log(obj.key);
} else {
  console.log("obj不存在或key缺失");
}
2. catch 塊必須接收 error 參數

嚴格模式下,catch塊必須聲明錯誤參數(如catch (error)),省略參數會報錯;非嚴格模式下省略參數雖不報錯,但無法獲取錯誤信息,不推薦。

"use strict";
try {
  JSON.parse("{invalid}");
} catch { // 錯誤:Strict mode code may not have catch clauses without a parameter
  console.log("解析失敗");
}
3. finally 中的 return 會覆蓋 try/catch 的返回值

若finally塊中有return,會覆蓋try/catch中的返回值,導致錯誤信息丟失,開發中需避免這種寫法。

function badFinally() {
  try {
    return "try返回值";
  } finally {
    return "finally返回值"; // 覆蓋try的返回值
  }
}
console.log(badFinally()); // 輸出:finally返回值
4. 無法捕獲異步回調中的錯誤

try/catch無法捕獲定時器、事件回調、Promise 回調中的錯誤(這些代碼屬於異步任務,執行時已脱離try塊的作用域),需在回調內部單獨捕獲。

// 錯誤示例:無法捕獲setTimeout中的錯誤
try {
  setTimeout(() => {
    console.log(undeclaredVar); // ReferenceError
  }, 100);
} catch (error) {
  console.error("捕獲錯誤:", error); // 不會執行
}

// 正確示例:在回調內部捕獲
setTimeout(() => {
  try {
    console.log(undeclaredVar);
  } catch (error) {
    console.error("回調內捕獲:", error.message);
  }
}, 100);

六、try/catch 與 Promise 錯誤處理的對比

場景 try/catch Promise.catch()
同步代碼 適用(首選) 不適用
async/await 異步代碼 適用(直觀) 適用(鏈式調用)
普通 Promise 回調 不適用(無法捕獲) 適用(首選)
錯誤傳播 需手動 throw 向上拋 自動向上傳播

示例對比:

// 1. async/await + try/catch(推薦)
async function demo1() {
  try {
    await fetch("https://api.example.com/err");
  } catch (error) {
    console.error("try/catch捕獲:", error);
  }
}

// 2. Promise + catch(傳統寫法)
function demo2() {
  fetch("https://api.example.com/err")
    .catch(error => console.error("Promise.catch捕獲:", error));
}

try/catch/finally是 JS 錯誤處理的 “標準方案”,核心價值是 “容錯”—— 讓程序在出錯時不崩潰,且能優雅處理錯誤、清理資源。掌握其核心規則(try試錯、catch處理、finally收尾),結合throw主動拋錯、區分錯誤類型,能大幅提升代碼的健壯性;同時需注意異步代碼的錯誤捕獲、避免過度使用try/catch,平衡代碼的容錯性和性能。