分析 JavaScript Error Handling 机制,包括 try-catch-finally 块的执行顺序,以及 Error 对象的结构和自定义错误类型。

各位观众老爷们,晚上好!我是今天的主讲人,很高兴能和大家一起聊聊 JavaScript 里的错误处理机制。这玩意儿,说重要也重要,说不重要…那是骗人的!代码不出错,那还是代码吗?所以,咱们今天就得好好研究一下,怎么优雅地处理 JavaScript 里的那些幺蛾子。

一、错误是个啥?Error 对象的结构

首先,咱们得认识一下错误长啥样。在 JavaScript 里,错误通常是以 Error 对象的形式存在的。这个 Error 对象可不是空壳子,它里面装着不少信息,能帮助咱们定位问题。

最常见的 Error 对象属性包括:

  • name: 错误类型的名称,比如 "TypeError"、"ReferenceError" 等。
  • message: 错误的描述信息,通常包含一些关于错误原因的说明。
  • stack: 错误堆栈信息,记录了错误发生时的调用栈,能帮助咱们追溯错误的源头。
try {
  // 故意制造一个错误
  console.log(undefinedVariable);
} catch (error) {
  console.log("错误名称:", error.name);
  console.log("错误信息:", error.message);
  console.log("错误堆栈:", error.stack);
}

运行上面的代码,你会发现控制台输出了类似下面的内容:

错误名称: ReferenceError
错误信息: undefinedVariable is not defined
错误堆栈: ReferenceError: undefinedVariable is not defined
    at <anonymous>:2:13

瞧见没? name 告诉咱们这是个 ReferenceErrormessage 告诉咱们 undefinedVariable 没定义, stack 则告诉咱们错误发生在第 2 行。这三个属性,尤其是 stack,简直是 Debug 神器!

除了这三个,有些浏览器还会提供一些额外的属性,比如 lineNumbercolumnNumber,直接告诉你错误在哪一行哪一列,更方便了。

二、try-catch-finally:错误处理三剑客

JavaScript 提供了 try-catch-finally 块来捕获和处理错误。这三个关键字就像错误处理的三剑客,各司其职,配合默契。

  • try: 这里面放的是你觉得可能会出错的代码。就像给代码穿上了一层防护罩,如果里面的代码真的出错了,程序不会直接崩溃,而是会跳到 catch 块。
  • catch: 这里面放的是错误处理的代码。当 try 块里的代码出错了,就会执行 catch 块里的代码。catch 块接收一个参数,通常命名为 error,它就是那个 Error 对象,包含了错误的信息。
  • finally: 这里面放的是无论有没有错误都要执行的代码。就像扫尾工作,不管 try 块里的代码有没有出错,finally 块里的代码都会执行。
try {
  // 可能会出错的代码
  let result = 10 / 0; // 除以 0 会报错
  console.log("结果:", result); // 这行代码可能不会执行
} catch (error) {
  // 处理错误的代码
  console.error("出错了:", error.message);
} finally {
  // 无论有没有错误都要执行的代码
  console.log("程序结束");
}

在这个例子中,10 / 0 会导致一个错误,程序会跳到 catch 块,输出错误信息,然后执行 finally 块,输出 "程序结束"。

三、try-catch-finally 的执行顺序:有点意思

try-catch-finally 的执行顺序可不是简单的线性执行,这里面有点小 trick。

  1. try 块先执行。 如果 try 块里的代码没有出错,程序会跳过 catch 块,直接执行 finally 块。
  2. 如果 try 块里出错了,程序会跳到 catch 块。 catch 块执行完毕后,会执行 finally 块。
  3. finally 块永远会执行。 除非你在 try 块或者 catch 块里写了 returnthrow 或者 break 语句,并且这些语句会中断程序的执行。即使这样,finally 块也仍然会执行,只不过会在 returnthrow 或者 break 语句执行之前执行。
function test() {
  try {
    console.log("try");
    return "try";
  } catch (error) {
    console.log("catch");
    return "catch";
  } finally {
    console.log("finally");
    //return "finally"; //如果打开这个注释,函数会返回"finally"
  }
}

console.log("返回值:", test());

运行结果:

try
finally
返回值: try
function test2() {
  try {
    console.log("try");
    throw new Error("error");
  } catch (error) {
    console.log("catch:", error.message);
    return "catch";
  } finally {
    console.log("finally");
    //return "finally"; //如果打开这个注释,函数会返回"finally"
  }
}

console.log("返回值:", test2());

运行结果:

try
catch: error
finally
返回值: catch
function test3() {
  try {
    console.log("try");
    throw new Error("error");
  } catch (error) {
    console.log("catch:", error.message);
    return "catch";
  } finally {
    console.log("finally");
    return "finally"; //如果打开这个注释,函数会返回"finally"
  }
}

console.log("返回值:", test3());

运行结果:

try
catch: error
finally
返回值: finally

注意到了吗?即使 try 块或者 catch 块里有 return 语句,finally 块还是会执行。而且,如果 finally 块里也有 return 语句,那么函数的返回值会是 finally 块里的 return 值,而不是 try 块或者 catch 块里的 return 值!

四、自定义错误类型:让错误更具描述性

JavaScript 内置了一些错误类型,比如 TypeErrorReferenceErrorSyntaxError 等。但有时候,这些内置的错误类型并不能完全描述咱们遇到的问题。这时候,咱们就可以自定义错误类型,让错误信息更具描述性。

自定义错误类型很简单,只需要创建一个新的类,继承自 Error 类即可。

class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = "ValidationError"; // 自定义错误名称
  }
}

function validateEmail(email) {
  if (!email.includes("@")) {
    throw new ValidationError("Invalid email format: missing @ symbol.");
  }
}

try {
  validateEmail("invalid-email");
} catch (error) {
  if (error instanceof ValidationError) {
    console.error("验证错误:", error.message);
  } else {
    console.error("其他错误:", error.message);
  }
}

在这个例子中,咱们创建了一个 ValidationError 类,继承自 Error 类,并自定义了错误名称为 "ValidationError"。这样,当 validateEmail 函数抛出错误时,咱们就可以通过 instanceof 运算符来判断错误类型,并进行相应的处理。

五、错误处理的最佳实践:让代码更健壮

错误处理不是随便写几行 try-catch 就完事了,它需要咱们认真思考,并遵循一些最佳实践,才能让代码更健壮、更可靠。

  • 不要忽略错误。 很多新手程序员喜欢忽略错误,直接把 catch 块留空,或者只写一句 console.log(error)。这其实是很危险的,因为错误可能会导致程序崩溃,或者产生意想不到的结果。正确的做法是,根据错误类型和上下文,进行相应的处理,比如重试、回滚、记录日志等。
  • 只捕获你能处理的错误。 不要试图捕获所有类型的错误,只捕获那些你能处理的错误。如果你不知道如何处理某个错误,那就让它继续向上抛,直到有地方能处理它。
  • 使用自定义错误类型。 自定义错误类型能让错误信息更具描述性,方便咱们定位问题和进行错误处理。
  • 在合适的地方使用 try-catch。 try-catch 块应该只包裹那些可能会出错的代码,不要滥用。
  • 使用 finally 块进行清理工作。 finally 块可以用来进行一些清理工作,比如关闭文件、释放资源等,确保无论有没有错误,这些工作都能完成。
  • Promise 的错误处理。 在使用 Promise 时,要使用 .catch() 方法来捕获错误。
fetch("https://api.example.com/data")
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  })
  .then(data => {
    console.log("数据:", data);
  })
  .catch(error => {
    console.error("请求失败:", error.message);
  });
  • Async/Await 的错误处理。 在使用 Async/Await 时,可以使用 try-catch 块来捕获错误。
async function fetchData() {
  try {
    const response = await fetch("https://api.example.com/data");
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const data = await response.json();
    console.log("数据:", data);
  } catch (error) {
    console.error("请求失败:", error.message);
  }
}

fetchData();
  • 全局错误处理。 可以使用 window.onerror 事件来捕获全局未处理的错误。
window.onerror = function(message, source, lineno, colno, error) {
  console.error("全局错误:", message, source, lineno, colno, error);
  // 可以将错误信息发送到服务器
  return true; // 阻止浏览器默认的错误处理
};

// 故意制造一个错误
console.log(undefinedVariable);
  • 使用 Lint 工具。 Lint 工具可以帮助咱们检查代码中的潜在错误,比如未定义的变量、未使用的变量等。

六、错误类型大扫盲

JavaScript 内置了很多错误类型,咱们来简单了解一下:

错误类型 描述 例子
Error 所有错误类型的基类。 throw new Error("Generic error");
EvalError eval() 函数中发生的错误。(已废弃,不用关心)
RangeError 数值超出允许的范围时发生的错误。 let arr = new Array(-1);
ReferenceError 引用未声明的变量时发生的错误。 console.log(undefinedVariable);
SyntaxError 语法错误。 eval("alert( 'Hello)");
TypeError 变量或参数不是预期类型时发生的错误。 null.property;
URIError encodeURI()decodeURI() 函数使用不正确时发生的错误。 decodeURI("%");
AggregateError 用于将多个错误包装在一个错误中。 当需要报告多个错误时,例如 Promise.all() 拒绝时。 `try {

await Promise.all([
Promise.reject(new Error("Some error")),
Promise.reject(new Error("Some other error")),
]);
} catch (e) {
console.log(e instanceof AggregateError); // true
console.log(e.errors); // [ Error: Some error, Error: Some other error ]
}` |

七、总结

错误处理是编程中不可或缺的一部分。通过学习 try-catch-finally 块的使用、Error 对象的结构、自定义错误类型以及错误处理的最佳实践,咱们可以写出更健壮、更可靠的代码。记住,不要害怕错误,要拥抱错误,因为错误是咱们进步的阶梯!

好了,今天的讲座就到这里。感谢各位的观看,希望对大家有所帮助! 记住,代码虐我千百遍,我待代码如初恋!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注