Skip to content

15|JavaScript 错误处理与异常

代码运行过程中不可避免地会出现错误:网络请求超时、用户输入非法、DOM 元素不存在。没有错误处理的代码在出错时会直接崩溃,页面卡死或功能不可用。try/catch 和合理的防御式编程,能把不可控的异常转为可控的错误响应。

一、try / catch / finally

基本结构

js
try {
  const data = JSON.parse(userInput);
  console.log(data.name);
} catch (err) {
  console.error("解析失败:", err.message);
} finally {
  console.log("无论成功失败,这里都会执行");
}

执行流程:

  1. try 块中的代码正常执行
  2. 如果抛出异常,执行权跳转到 catch
  3. 无论是否发生异常,finally 块都会执行

catch 参数(上面例子中的 err)是一个 Error 对象,包含:

属性含义
err.message错误描述信息
err.name错误类型(如 SyntaxErrorTypeError
err.stack调用栈追踪(调试用)

抛出错误

js
function divide(a, b) {
  if (b === 0) {
    throw new Error("除数不能为 0");
  }
  return a / b;
}

try {
  divide(10, 0);
} catch (err) {
  console.error(err.message);   // "除数不能为 0"
}

可以用内置的 Error 子类表达更具体的错误:

js
throw new TypeError("参数必须是数字");
throw new RangeError("索引超出范围");
throw new SyntaxError("JSON 格式错误");

二、常见错误类型

类型触发场景
SyntaxError语法错误,如非法的 JSON、代码解析失败
TypeError类型错误,如对 null 调用方法、给只读属性赋值
ReferenceError引用错误,如访问未声明的变量
RangeError范围错误,如数组长度为负数、递归过深
URIErrorURI 编码/解码错误
js
// TypeError
const obj = null;
obj.name;              // TypeError: Cannot read property 'name' of null

// ReferenceError
console.log(notDefined);   // ReferenceError: notDefined is not defined

// RangeError
const arr = new Array(-1);   // RangeError: Invalid array length

三、防御式编程

不是所有错误都需要 try/catch。对于可预判的情况,先检查再操作:

检查 null / undefined

js
// 不推荐:依赖 try/catch
function getServerName(data) {
  try {
    return data.server.name;
  } catch {
    return "未知";
  }
}

// 推荐:先检查
function getServerName(data) {
  if (data?.server?.name) {
    return data.server.name;
  }
  return "未知";
}

可选链 ?. 在链上任意一层为 nullundefined 时安全返回 undefined,比 try/catch 更简洁。

检查数组长度

js
function getFirstItem(arr) {
  if (!Array.isArray(arr) || arr.length === 0) {
    return null;
  }
  return arr[0];
}

参数校验

js
function connect(host, port) {
  if (typeof host !== "string" || host.length === 0) {
    throw new TypeError("host 必须是非空字符串");
  }
  if (typeof port !== "number" || port < 1 || port > 65535) {
    throw new RangeError("port 必须是 1~65535 之间的数字");
  }
  // ...
}

四、异步错误处理

Promise 的 catch

js
fetch("/api/servers")
  .then((response) => response.json())
  .then((data) => {
    console.log(data);
  })
  .catch((err) => {
    console.error("请求失败:", err.message);
  });

catch 捕获链上任意一步抛出的错误。

async / await 的 try/catch

js
async function loadServers() {
  try {
    const response = await fetch("/api/servers");
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    const data = await response.json();
    return data;
  } catch (err) {
    console.error("加载服务器列表失败:", err.message);
    return [];   // 返回空数组,让调用方可以继续执行
  }
}

await 后面如果 Promise 被拒绝(rejected),会抛出异常,需要用 try/catch 包裹。

五、全局错误捕获

window.onerror

捕获未处理的同步错误:

js
window.onerror = function (message, source, lineno, colno, error) {
  console.error("全局错误:", message, "位于", `${source}:${lineno}`);
  return true;   // 阻止默认错误提示(控制台红字)
};

unhandledrejection

捕获未处理的 Promise 拒绝:

js
window.addEventListener("unhandledrejection", (event) => {
  console.error("未处理的 Promise 错误:", event.reason);
  event.preventDefault();
});

全局捕获是最后一道防线,不能替代具体的错误处理。生产环境可以把全局错误上报到日志平台,但每个功能模块仍应自己处理可预期的错误。

六、常见错误

吞掉所有错误

js
// 错误:catch 里什么都不做,错误被静默吞掉
try {
  riskyOperation();
} catch {
  // 空 catch
}

// 正确:至少记录日志,或返回错误状态
try {
  riskyOperation();
} catch (err) {
  console.error("操作失败:", err.message);
  showErrorToUser("操作失败,请稍后重试");
}

在 finally 中 return

js
function test() {
  try {
    return "try";
  } finally {
    return "finally";   // 会覆盖 try 中的返回值
  }
}

console.log(test());   // "finally"

finally 中的 return 会覆盖 trycatch 中的返回值。避免在 finallyreturn

用 throw 控制流程

js
// 不建议:throw 不是 goto
try {
  if (!user) throw new Error("未登录");
  if (!permission) throw new Error("无权限");
  doSomething();
} catch (err) {
  handleError(err);
}

// 建议:普通条件判断
try {
  if (!user) {
    redirectToLogin();
    return;
  }
  if (!permission) {
    showForbidden();
    return;
  }
  doSomething();
} catch (err) {
  handleError(err);
}

throw 用于真正的异常情况,正常的分支逻辑用 if/return